diff --git a/packages/cli/package.json b/packages/cli/package.json index 8ef670e3..9f54a1ff 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -40,6 +40,7 @@ "@types/blessed": "^0.1.25", "@types/swagger-ui-express": "^4.1.6", "@vckit/bitstringStatusList": "workspace:^", + "@vckit/credential-data-integrity": "workspace:^", "@vckit/core-types": "workspace:^", "@vckit/credential-merkle-disclosure-proof": "workspace:1.0.0-beta.7", "@vckit/credential-oa": "workspace:1.0.0-beta.7", diff --git a/packages/credential-data-integrity/README.md b/packages/credential-data-integrity/README.md new file mode 100644 index 00000000..5169d2b9 --- /dev/null +++ b/packages/credential-data-integrity/README.md @@ -0,0 +1,190 @@ +# Credential Data Integrity Plugin + +This plugin draws inspiration from [eddsa-rdfc-2022-cryptosuite](@digitalbazaar/eddsa-rdfc-2022-cryptosuite) and is implemented as a plugin for [Veramo](https://veramo.io/). It adheres to the [Data Integrity EdDSA Cryptosuites v1.0](https://www.w3.org/TR/vc-di-eddsa/) specification to generate a Credential Data Integrity for credentials. + +This plugin contains a message handler for issuing and verifying credentials that adhere to W3C standards. + +## Current state + +This plugin is in the early stages of development and is not yet ready for production use. It is not recommended to use this plugin in a production environment. + +Currently, this plugin cannot be used alongside other plugins due to limitations in the Veramo core plugins. We plan to update the plugin in the future to enable compatibility with other plugins. + +## Setup + +If you want to try using the Credential Data Integrity plugin with a `did:web` DID, you can use the following example to create a Web DID Document router. + +- Create a Web DID Document router. You can use the following docs: [Set up Web DID Document Router](./web-did-doc-router-example.md) + +- Declare the plugin in your Veramo [agent](../../agent.yml): + +```yaml +credentialIssuerLD: + $require: '@veramo/credential-ld#CredentialIssuerLD' + $args: + - suites: + - $require: '@vckit/credential-data-integrity#VCkitEddsaRdfc2022' + # others should be included here + contextMaps: + - $require: '@vckit/credential-data-integrity?t=object#contexts' + # others should be included here +``` + +## Issue the Credential example + +You can issue a new verifiable credential by using the `/agent/routeCreationVerifiableCredential` API endpoint with the proof format set to `lds`. The issuer can be a `did:web`. + +The example below shows how to issue a credential with the Data Integrity Integrity plugin. You can replace the `issuer` and `credentialSubject` with your own data. + +### Verifiable Credential Data Model V1 + +```curl +curl --request POST \ + --url http://localhost:3332/agent/routeCreationVerifiableCredential \ + --header 'Content-Type: application/json' \ + --data '{ + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/data-integrity/v2" + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "did:web:example.com", + "issuanceDate": "2021-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:456", + "name": "John Doe" + } + }, + "proofFormat": "lds", + "save": true, + "fetchRemoteContexts": true +}' +``` + +### Verifiable credential data model V2 + +```curl +curl --request POST \ + --url http://localhost:3332/agent/routeCreationVerifiableCredential \ + --header 'Content-Type: application/json' \ + --data '{ + "credential": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/data-integrity/v2" + ], + "id": "http://university.example/credentials/1872", + "type": [ + "VerifiableCredential", + "ExampleAlumniCredential" + ], + "issuer": "did:web:example.com", + "validFrom": "2010-01-01T19:23:24Z", + "credentialSchema": { + "id": "https://example.org/examples/degree.json", + "type": "JsonSchema" + }, + "credentialSubject": { + "id": "did:example:123", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + } + }, + "proofFormat": "lds", + "save": true, + "fetchRemoteContexts": true +}' +``` + +## Verify the credential example + +You can verify the verifiable credential by using the `/agent/routeVerificationCredential` API endpoint. + +The examples below show how to verify the verifiable credentials in the examples above using the Data Integrity proof. + +### Verifiable Credential Data Model V1 + +```curl +curl --request POST \ + --url http://localhost:3332/agent/routeVerificationCredential \ + --header 'Content-Type: application/json' \ + --data '{ + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/data-integrity/v2" + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "did:web:example.com", + "issuanceDate": "2021-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:456", + "name": "John Doe" + }, + "proof": { + "type": "DataIntegrityProof", + "created": "2024-07-21T15:39:40Z", + "verificationMethod": "did:web:example.com#14fe3440c6d669edd8a63dc92b571fb0973fd4b832444014e69bcf8cebd38853", + "cryptosuite": "eddsa-rdfc-2022", + "proofPurpose": "assertionMethod", + "proofValue": "z2S6qW6k6M6eXuqGkX5vdydqveSNVBSZf46MxxjY5ukv8gL741pos3yywT9mGTjKJzdjxQvaCSSVtCAngoAWQzNnq" + } + }, + "fetchRemoteContexts": true +}' +``` + +### Verifiable Credential Data Model V2 + +```curl +curl --request POST \ + --url http://localhost:3332/agent/routeVerificationCredential \ + --header 'Content-Type: application/json' \ + --data '{ + "credential": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/data-integrity/v2" + ], + "id": "http://university.example/credentials/1872", + "type": [ + "VerifiableCredential", + "ExampleAlumniCredential" + ], + "issuer": "did:web:example.com", + "validFrom": "2010-01-01T19:23:24Z", + "credentialSchema": { + "id": "https://example.org/examples/degree.json", + "type": "JsonSchema" + }, + "credentialSubject": { + "id": "did:example:123", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + }, + "issuanceDate": "2024-07-25T09:25:22.788Z", + "proof": { + "type": "DataIntegrityProof", + "created": "2024-07-25T09:25:23Z", + "verificationMethod": "did:web:example.com#a0b90e4ec2c9fbc63c50f230b98ea8335af1da5bba9472684519ae7da11273d6", + "cryptosuite": "eddsa-rdfc-2022", + "proofPurpose": "assertionMethod", + "proofValue": "zbS3i4uaUzAw1J7Eb544Tgqfo8azhJkx3jGS5wtk4WCBe2CkAZyLEnX7Au7n3anEtTWaG9f283NF2rzJEsxFKjPE" + } + }, + "fetchRemoteContexts": true +}' +``` diff --git a/packages/credential-data-integrity/__tests__/default-contexts.test.ts b/packages/credential-data-integrity/__tests__/default-contexts.test.ts new file mode 100644 index 00000000..f3cf34ea --- /dev/null +++ b/packages/credential-data-integrity/__tests__/default-contexts.test.ts @@ -0,0 +1,51 @@ +import { contexts } from '../src'; +import contextCredentialV1 from '../src/contexts/www.w3.org_2018_credentials_v1.json'; +import contextCredentialV2 from '../src/contexts/www.w3.org_ns_credentials_v2.json'; +import contextDidV1 from '../src/contexts/www.w3.org_ns_did_v1.json'; +import contextMultikey from '../src/contexts/w3id.org_security_multikey_v1.json'; +import contextDataIntegrityV1 from '../src/contexts/w3id.org_security_data-integrity_v1.json'; +import contextDataIntegrityV2 from '../src/contexts/w3id.org_security_data-integrity_v2.json'; + +describe('Contexts Map', () => { + // Test to verify that the map contains exactly six contexts + test('should contain the correct number of contexts', () => { + // Check if the size of the map is 6 + expect(contexts.size).toBe(6); + }); + + // Test to verify the mapping for 'https://www.w3.org/2018/credentials/v1' + test('should map "https://www.w3.org/2018/credentials/v1" to the correct context', () => { + // Check if the map returns the correct JSON object for the given URL + expect(contexts.get('https://www.w3.org/2018/credentials/v1')).toBe(contextCredentialV1); + }); + + // Test to verify the mapping for 'https://www.w3.org/ns/credentials/v2' + test('should map "https://www.w3.org/ns/credentials/v2" to the correct context', () => { + // Check if the map returns the correct JSON object for the given URL + expect(contexts.get('https://www.w3.org/ns/credentials/v2')).toBe(contextCredentialV2); + }); + + // Test to verify the mapping for 'https://www.w3.org/ns/did/v1' + test('should map "https://www.w3.org/ns/did/v1" to the correct context', () => { + // Check if the map returns the correct JSON object for the given URL + expect(contexts.get('https://www.w3.org/ns/did/v1')).toBe(contextDidV1); + }); + + // Test to verify the mapping for 'https://w3id.org/security/multikey/v1' + test('should map "https://w3id.org/security/multikey/v1" to the correct context', () => { + // Check if the map returns the correct JSON object for the given URL + expect(contexts.get('https://w3id.org/security/multikey/v1')).toBe(contextMultikey); + }); + + // Test to verify the mapping for 'https://w3id.org/security/data-integrity/v1' + test('should map "https://w3id.org/security/data-integrity/v1" to the correct context', () => { + // Check if the map returns the correct JSON object for the given URL + expect(contexts.get('https://w3id.org/security/data-integrity/v1')).toBe(contextDataIntegrityV1); + }); + + // Test to verify the mapping for 'https://w3id.org/security/data-integrity/v2' + test('should map "https://w3id.org/security/data-integrity/v2" to the correct context', () => { + // Check if the map returns the correct JSON object for the given URL + expect(contexts.get('https://w3id.org/security/data-integrity/v2')).toBe(contextDataIntegrityV2); + }); +}); \ No newline at end of file diff --git a/packages/credential-data-integrity/api-extractor.json b/packages/credential-data-integrity/api-extractor.json new file mode 100644 index 00000000..409d7f16 --- /dev/null +++ b/packages/credential-data-integrity/api-extractor.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "apiReport": { + "enabled": true, + "reportFolder": "./api", + "reportTempFolder": "./api" + }, + + "docModel": { + "enabled": true, + "apiJsonFilePath": "./api/.api.json" + }, + + "dtsRollup": { + "enabled": false + }, + "mainEntryPointFilePath": "/build/index.d.ts" +} diff --git a/packages/credential-data-integrity/package.json b/packages/credential-data-integrity/package.json new file mode 100644 index 00000000..d39d0bc2 --- /dev/null +++ b/packages/credential-data-integrity/package.json @@ -0,0 +1,44 @@ +{ + "name": "@vckit/credential-data-integrity", + "description": "Veramo plugin for working with W3C JSON-LD Verifiable Credentials.", + "version": "1.0.0", + "type": "module", + "main": "build/index.js", + "exports": { + ".": "./build/index.js" + }, + "types": "build/index.d.ts", + "scripts": { + "build": "tsc" + }, + "dependencies": { + "@digitalbazaar/data-integrity": "^2.1.0", + "@digitalbazaar/ed25519-multikey": "^1.1.0", + "@digitalbazaar/eddsa-rdfc-2022-cryptosuite": "^1.0.1", + "@vckit/core-types": "workspace:*", + "@veramo/utils": "5.5.3", + "@veramo/credential-ld": "link:../../.tmp_npm/veramo/packages/credential-ld" + }, + "devDependencies": { + "typescript": "5.3.3" + }, + "files": [ + "build/**/*", + "src/**/*", + "contexts/**/*.json", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "keywords": [ + "Veramo", + "DID", + "Verifiable Credential", + "JSON-LD", + "Data Integrity", + "MultiKey", + "vc-ld-json", + "veramo-plugin" + ] +} diff --git a/packages/credential-data-integrity/src/contexts/w3id.org_security_data-integrity_v1.json b/packages/credential-data-integrity/src/contexts/w3id.org_security_data-integrity_v1.json new file mode 100644 index 00000000..86a00224 --- /dev/null +++ b/packages/credential-data-integrity/src/contexts/w3id.org_security_data-integrity_v1.json @@ -0,0 +1,78 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "@protected": true, + "digestMultibase": { + "@id": "https://w3id.org/security#digestMultibase", + "@type": "https://w3id.org/security#multibase" + }, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "DataIntegrityProof": { + "@id": "https://w3id.org/security#DataIntegrityProof", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "cryptosuite": "https://w3id.org/security#cryptosuite", + "proofValue": { + "@id": "https://w3id.org/security#proofValue", + "@type": "https://w3id.org/security#multibase" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} \ No newline at end of file diff --git a/packages/credential-data-integrity/src/contexts/w3id.org_security_data-integrity_v2.json b/packages/credential-data-integrity/src/contexts/w3id.org_security_data-integrity_v2.json new file mode 100644 index 00000000..a7ad53b5 --- /dev/null +++ b/packages/credential-data-integrity/src/contexts/w3id.org_security_data-integrity_v2.json @@ -0,0 +1,81 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "@protected": true, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "DataIntegrityProof": { + "@id": "https://w3id.org/security#DataIntegrityProof", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "previousProof": { + "@id": "https://w3id.org/security#previousProof", + "@type": "@id" + }, + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "cryptosuite": { + "@id": "https://w3id.org/security#cryptosuite", + "@type": "https://w3id.org/security#cryptosuiteString" + }, + "proofValue": { + "@id": "https://w3id.org/security#proofValue", + "@type": "https://w3id.org/security#multibase" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} \ No newline at end of file diff --git a/packages/credential-data-integrity/src/contexts/w3id.org_security_multikey_v1.json b/packages/credential-data-integrity/src/contexts/w3id.org_security_multikey_v1.json new file mode 100644 index 00000000..0125c747 --- /dev/null +++ b/packages/credential-data-integrity/src/contexts/w3id.org_security_multikey_v1.json @@ -0,0 +1,35 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "@protected": true, + "Multikey": { + "@id": "https://w3id.org/security#Multikey", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "revoked": { + "@id": "https://w3id.org/security#revoked", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "publicKeyMultibase": { + "@id": "https://w3id.org/security#publicKeyMultibase", + "@type": "https://w3id.org/security#multibase" + }, + "secretKeyMultibase": { + "@id": "https://w3id.org/security#secretKeyMultibase", + "@type": "https://w3id.org/security#multibase" + } + } + } + } +} \ No newline at end of file diff --git a/packages/credential-data-integrity/src/contexts/www.w3.org_2018_credentials_v1.json b/packages/credential-data-integrity/src/contexts/www.w3.org_2018_credentials_v1.json new file mode 100644 index 00000000..e5c3a2b0 --- /dev/null +++ b/packages/credential-data-integrity/src/contexts/www.w3.org_2018_credentials_v1.json @@ -0,0 +1,315 @@ +{ + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "VerifiableCredential": { + "@id": "https://www.w3.org/2018/credentials#VerifiableCredential", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "credentialSchema": { + "@id": "cred:credentialSchema", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "JsonSchemaValidator2018": "cred:JsonSchemaValidator2018" + } + }, + "credentialStatus": { + "@id": "cred:credentialStatus", + "@type": "@id" + }, + "credentialSubject": { + "@id": "cred:credentialSubject", + "@type": "@id" + }, + "evidence": { + "@id": "cred:evidence", + "@type": "@id" + }, + "expirationDate": { + "@id": "cred:expirationDate", + "@type": "xsd:dateTime" + }, + "holder": { + "@id": "cred:holder", + "@type": "@id" + }, + "issued": { + "@id": "cred:issued", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "cred:issuer", + "@type": "@id" + }, + "issuanceDate": { + "@id": "cred:issuanceDate", + "@type": "xsd:dateTime" + }, + "proof": { + "@id": "sec:proof", + "@type": "@id", + "@container": "@graph" + }, + "refreshService": { + "@id": "cred:refreshService", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "ManualRefreshService2018": "cred:ManualRefreshService2018" + } + }, + "termsOfUse": { + "@id": "cred:termsOfUse", + "@type": "@id" + }, + "validFrom": { + "@id": "cred:validFrom", + "@type": "xsd:dateTime" + }, + "validUntil": { + "@id": "cred:validUntil", + "@type": "xsd:dateTime" + } + } + }, + "VerifiablePresentation": { + "@id": "https://www.w3.org/2018/credentials#VerifiablePresentation", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + "holder": { + "@id": "cred:holder", + "@type": "@id" + }, + "proof": { + "@id": "sec:proof", + "@type": "@id", + "@container": "@graph" + }, + "verifiableCredential": { + "@id": "cred:verifiableCredential", + "@type": "@id", + "@container": "@graph" + } + } + }, + "EcdsaSecp256k1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256k1Signature2019", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "EcdsaSecp256r1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256r1Signature2019", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "Ed25519Signature2018": { + "@id": "https://w3id.org/security#Ed25519Signature2018", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "RsaSignature2018": { + "@id": "https://w3id.org/security#RsaSignature2018", + "@context": { + "@version": 1.1, + "@protected": true, + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + } + } +} diff --git a/packages/credential-data-integrity/src/contexts/www.w3.org_ns_credentials_v2.json b/packages/credential-data-integrity/src/contexts/www.w3.org_ns_credentials_v2.json new file mode 100644 index 00000000..e61836aa --- /dev/null +++ b/packages/credential-data-integrity/src/contexts/www.w3.org_ns_credentials_v2.json @@ -0,0 +1,328 @@ +{ + "@context": { + "@protected": true, + "@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#", + + "id": "@id", + "type": "@type", + + "kid": { + "@id": "https://www.iana.org/assignments/jose#kid", + "@type": "@id" + }, + "iss": { + "@id": "https://www.iana.org/assignments/jose#iss", + "@type": "@id" + }, + "sub": { + "@id": "https://www.iana.org/assignments/jose#sub", + "@type": "@id" + }, + "jku": { + "@id": "https://www.iana.org/assignments/jose#jku", + "@type": "@id" + }, + "x5u": { + "@id": "https://www.iana.org/assignments/jose#x5u", + "@type": "@id" + }, + "aud": { + "@id": "https://www.iana.org/assignments/jwt#aud", + "@type": "@id" + }, + "exp": { + "@id": "https://www.iana.org/assignments/jwt#exp", + "@type": "https://www.w3.org/2001/XMLSchema#nonNegativeInteger" + }, + "nbf": { + "@id": "https://www.iana.org/assignments/jwt#nbf", + "@type": "https://www.w3.org/2001/XMLSchema#nonNegativeInteger" + }, + "iat": { + "@id": "https://www.iana.org/assignments/jwt#iat", + "@type": "https://www.w3.org/2001/XMLSchema#nonNegativeInteger" + }, + "cnf": { + "@id": "https://www.iana.org/assignments/jwt#cnf", + "@context": { + "@protected": true, + "kid": { + "@id": "https://www.iana.org/assignments/jwt#kid", + "@type": "@id" + }, + "jwk": { + "@id": "https://www.iana.org/assignments/jwt#jwk", + "@type": "@json" + } + } + }, + "_sd_alg": { + "@id": "https://www.iana.org/assignments/jwt#_sd_alg" + }, + "_sd": { + "@id": "https://www.iana.org/assignments/jwt#_sd" + }, + "...": { + "@id": "https://www.iana.org/assignments/jwt#..." + }, + + "digestSRI": { + "@id": "https://www.w3.org/2018/credentials#digestSRI", + "@type": "https://www.w3.org/2018/credentials#sriString" + }, + "digestMultibase": { + "@id": "https://w3id.org/security#digestMultibase", + "@type": "https://w3id.org/security#multibase" + }, + + "mediaType": { + "@id": "https://schema.org/encodingFormat" + }, + + "description": "https://schema.org/description", + "name": "https://schema.org/name", + + "EnvelopedVerifiableCredential": + "https://www.w3.org/2018/credentials#EnvelopedVerifiableCredential", + + "VerifiableCredential": { + "@id": "https://www.w3.org/2018/credentials#VerifiableCredential", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "credentialSchema": { + "@id": "https://www.w3.org/2018/credentials#credentialSchema", + "@type": "@id" + }, + "credentialStatus": { + "@id": "https://www.w3.org/2018/credentials#credentialStatus", + "@type": "@id" + }, + "credentialSubject": { + "@id": "https://www.w3.org/2018/credentials#credentialSubject", + "@type": "@id" + }, + "description": "https://schema.org/description", + "evidence": { + "@id": "https://www.w3.org/2018/credentials#evidence", + "@type": "@id" + }, + "validFrom": { + "@id": "https://www.w3.org/2018/credentials#validFrom", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "validUntil": { + "@id": "https://www.w3.org/2018/credentials#validUntil", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "issuer": { + "@id": "https://www.w3.org/2018/credentials#issuer", + "@type": "@id" + }, + "name": "https://schema.org/name", + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "refreshService": { + "@id": "https://www.w3.org/2018/credentials#refreshService", + "@type": "@id" + }, + "termsOfUse": { + "@id": "https://www.w3.org/2018/credentials#termsOfUse", + "@type": "@id" + }, + "confidenceMethod": { + "@id": "https://www.w3.org/2018/credentials#confidenceMethod", + "@type": "@id" + }, + "relatedResource": { + "@id": "https://www.w3.org/2018/credentials#relatedResource", + "@type": "@id" + } + } + }, + + "VerifiablePresentation": { + "@id": "https://www.w3.org/2018/credentials#VerifiablePresentation", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "holder": { + "@id": "https://www.w3.org/2018/credentials#holder", + "@type": "@id" + }, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "verifiableCredential": { + "@id": "https://www.w3.org/2018/credentials#verifiableCredential", + "@type": "@id", + "@container": "@graph", + "@context": null + }, + "termsOfUse": { + "@id": "https://www.w3.org/2018/credentials#termsOfUse", + "@type": "@id" + } + } + }, + + "JsonSchemaCredential": "https://www.w3.org/2018/credentials#JsonSchemaCredential", + + "JsonSchema": { + "@id": "https://www.w3.org/2018/credentials#JsonSchema", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "jsonSchema": { + "@id": "https://w3.org/2018/credentials#jsonSchema", + "@type": "@json" + } + } + }, + + "BitstringStatusListCredential": "https://www.w3.org/ns/credentials/status#BitstringStatusListCredential", + + "BitstringStatusList": { + "@id": "https://www.w3.org/ns/credentials/status#BitstringStatusList", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "statusPurpose": + "https://www.w3.org/ns/credentials/status#statusPurpose", + "encodedList": { + "@id": "https://www.w3.org/ns/credentials/status#encodedList", + "@type": "https://w3id.org/security#multibase" + }, + "ttl": "https://www.w3.org/ns/credentials/status#ttl", + "statusReference": { + "@id": "https://www.w3.org/ns/credentials/status#statusReference", + "@type": "@id" + }, + "statusSize": { + "@id": "https://www.w3.org/ns/credentials/status#statusSize", + "@type": "https://www.w3.org/2001/XMLSchema#positiveInteger" + }, + "statusMessage": { + "@id": "https://www.w3.org/ns/credentials/status#statusMessage", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "status": "https://www.w3.org/ns/credentials/status#status", + "message": "https://www.w3.org/ns/credentials/status#message" + } + } + } + }, + + "BitstringStatusListEntry": { + "@id": + "https://www.w3.org/ns/credentials/status#BitstringStatusListEntry", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "statusPurpose": + "https://www.w3.org/ns/credentials/status#statusPurpose", + "statusListIndex": + "https://www.w3.org/ns/credentials/status#statusListIndex", + "statusListCredential": { + "@id": + "https://www.w3.org/ns/credentials/status#statusListCredential", + "@type": "@id" + } + } + }, + + "DataIntegrityProof": { + "@id": "https://w3id.org/security#DataIntegrityProof", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "previousProof": { + "@id": "https://w3id.org/security#previousProof", + "@type": "@id" + }, + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "cryptosuite": { + "@id": "https://w3id.org/security#cryptosuite", + "@type": "https://w3id.org/security#cryptosuiteString" + }, + "proofValue": { + "@id": "https://w3id.org/security#proofValue", + "@type": "https://w3id.org/security#multibase" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} \ No newline at end of file diff --git a/packages/credential-data-integrity/src/contexts/www.w3.org_ns_did_v1.json b/packages/credential-data-integrity/src/contexts/www.w3.org_ns_did_v1.json new file mode 100644 index 00000000..cb5bfe6f --- /dev/null +++ b/packages/credential-data-integrity/src/contexts/www.w3.org_ns_did_v1.json @@ -0,0 +1,57 @@ +{ + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "alsoKnownAs": { + "@id": "https://www.w3.org/ns/activitystreams#alsoKnownAs", + "@type": "@id" + }, + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + }, + "service": { + "@id": "https://www.w3.org/ns/did#service", + "@type": "@id", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "serviceEndpoint": { + "@id": "https://www.w3.org/ns/did#serviceEndpoint", + "@type": "@id" + } + } + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } +} diff --git a/packages/credential-data-integrity/src/default-contexts.ts b/packages/credential-data-integrity/src/default-contexts.ts new file mode 100644 index 00000000..308639d0 --- /dev/null +++ b/packages/credential-data-integrity/src/default-contexts.ts @@ -0,0 +1,16 @@ +import contextCredentialV1 from './contexts/www.w3.org_2018_credentials_v1.json' assert { type: 'json' } +import contextCredentialV2 from './contexts/www.w3.org_ns_credentials_v2.json' assert { type: 'json' } +import contextDidV1 from './contexts/www.w3.org_ns_did_v1.json' assert { type: 'json' } +import contextMultikey from './contexts/w3id.org_security_multikey_v1.json' assert { type: 'json' } +import contextDataIntegrityV1 from './contexts/w3id.org_security_data-integrity_v1.json' assert { type: 'json' } +import contextDataIntegrityV2 from './contexts/w3id.org_security_data-integrity_v2.json' assert { type: 'json' } + +// @ts-ignore +export const contexts = new Map([ + ['https://www.w3.org/2018/credentials/v1', contextCredentialV1], + ['https://www.w3.org/ns/credentials/v2', contextCredentialV2], + ['https://www.w3.org/ns/did/v1', contextDidV1], + ['https://w3id.org/security/multikey/v1', contextMultikey], + ['https://w3id.org/security/data-integrity/v1', contextDataIntegrityV1], + ['https://w3id.org/security/data-integrity/v2', contextDataIntegrityV2], +]) diff --git a/packages/credential-data-integrity/src/index.ts b/packages/credential-data-integrity/src/index.ts new file mode 100644 index 00000000..b3116836 --- /dev/null +++ b/packages/credential-data-integrity/src/index.ts @@ -0,0 +1,2 @@ +export { VCkitEddsaRdfc2022 } from './suites/VCkitEddsaRdfc2022.js' +export { contexts } from './default-contexts.js' diff --git a/packages/credential-data-integrity/src/module-types/index.d.ts b/packages/credential-data-integrity/src/module-types/index.d.ts new file mode 100644 index 00000000..4b4c288e --- /dev/null +++ b/packages/credential-data-integrity/src/module-types/index.d.ts @@ -0,0 +1,8 @@ +declare module '@digitalbazaar/ed25519-multikey'; +declare module '@digitalbazaar/eddsa-rdfc-2022-cryptosuite'; +declare module '@digitalbazaar/data-integrity'; + +declare module "*.json" { + const content: any; + export default content; +} diff --git a/packages/credential-data-integrity/src/suites/VCkitEddsaRdfc2022.ts b/packages/credential-data-integrity/src/suites/VCkitEddsaRdfc2022.ts new file mode 100644 index 00000000..0d7bc950 --- /dev/null +++ b/packages/credential-data-integrity/src/suites/VCkitEddsaRdfc2022.ts @@ -0,0 +1,168 @@ +import { + bytesToBase64, + hexToBytes, + base64ToBytes, + bytesToBase58, +} from '@veramo/utils'; +import { VeramoLdSignature } from '@veramo/credential-ld'; +import { + CredentialPayload, + IAgentContext, + DIDDocument, + DIDDocComponent, + IKey, + TKeyType, + IResolver, + IKeyManager, +} from '@vckit/core-types'; +import * as Ed25519Multikey from '@digitalbazaar/ed25519-multikey'; +import { DataIntegrityProof } from '@digitalbazaar/data-integrity'; +import { cryptosuite as eddsaRdfc2022CryptoSuite } from '@digitalbazaar/eddsa-rdfc-2022-cryptosuite'; + +/** + * VCKit wrapper for the Ed25519Multikey suite by @digitalbazaar + */ +export class VCkitEddsaRdfc2022 extends VeramoLdSignature { + /** + * This method is called when create a suite's verification key. + * @returns the supported verification type (Multikey) + */ + getSupportedVerificationType(): string { + return 'Multikey'; + } + + /** + * This method is called in the custom signer object in the suite. + * @returns the supported Veramo key type (Ed25519) + */ + getSupportedVeramoKeyType(): TKeyType { + return 'Ed25519'; + } + + /** + * This method is called before the credential is signed. + * If you need to modify the suite before signing, you can do it here. + * @param key is the Cryptographic key to be used for signing + * @param issuerDid is the DID of the issuer + * @param verificationMethodId is the ID of the verification method + * @param context is the agent context + * @returns the suite for signing + */ + async getSuiteForSigning( + key: IKey, + issuerDid: string, + verificationMethodId: string, + context: IAgentContext>, + ) { + // Custom signer for the suite + const signer = { + // Required for the DataIntegrityProof + id: verificationMethodId, + // Required for the DataIntegrityProof + algorithm: this.getSupportedVeramoKeyType(), // Ed25519 + sign: async (args: { data: Uint8Array }): Promise => { + const messageString = bytesToBase64(args.data); + const signature = await context.agent.keyManagerSign({ + keyRef: key.kid, + data: messageString, + encoding: 'base64', + }); + return base64ToBytes(signature); + }, + }; + + /** + * The public key multibase must consist of a binary value that starts with the two-byte prefix '0xed01' + * For details see: https://www.w3.org/TR/vc-di-eddsa/#multikey + */ + const MULTICODEC_PUB_HEADER = new Uint8Array([0xed, 0x01]); + const publicKeyMultibase = `z${bytesToBase58(new Uint8Array([...MULTICODEC_PUB_HEADER, ...hexToBytes(key.publicKeyHex)]))}`; + + const verificationKey = await Ed25519Multikey.from({ + id: verificationMethodId, + controller: issuerDid, + type: this.getSupportedVerificationType(), // Multikey + publicKeyMultibase, + }); + + // overwrite the signer since we're not passing the private key + verificationKey.signer = () => signer; + + const suite = new DataIntegrityProof({ + key: verificationKey, // use Ed25519Multikey suite for signing + signer, + cryptosuite: eddsaRdfc2022CryptoSuite, + }); + + suite.ensureSuiteContext = ({ + document, + }: { + document: any; + addSuiteContext: boolean; + }) => { + // necessary context for the suite + const credentialContextUrls = new Set([ + 'https://w3id.org/security/multikey/v1', + 'https://www.w3.org/2018/credentials/v1', + 'https://www.w3.org/ns/credentials/v2', + 'https://w3id.org/security/data-integrity/v1', + 'https://w3id.org/security/data-integrity/v2', + ]); + const firstContext = Array.isArray(document['@context']) + ? document['@context'][0] + : document['@context']; + if (!credentialContextUrls.has(firstContext)) { + throw new TypeError( + "The document to be signed must contain this suite's @context, " + + `${JSON.stringify(document['@context'], null, 2)}.`, + ); + } + }; + + return suite; + } + + /** + * This method is called before verifying the credential. + * If you need to change the suite before verifying, you can do it here. + * @returns the suite for verification + */ + getSuiteForVerification() { + // type of the suite is DataIntegrityProof + return new DataIntegrityProof({ cryptosuite: eddsaRdfc2022CryptoSuite }); + } + + /** + * This method is called before the credential is signed. + * If you need to modify the credential before signing, you can do it here. + * @param credential is the credential to be signed + */ + preSigningCredModification(credential: CredentialPayload) {} + + /** + * This method is called before the DID resolution. + * If you need to modify the DID Document before resolving, you can do it here. + * @param didUrl is the DID URL of the DID Document + * @param didDoc is the DID Document + * @param context is the agent context + * @returns the DID Document or the DID Component (e.g. Verification Method) + */ + async preDidResolutionModification( + didUrl: string, + didDoc: DIDDocument, + context: IAgentContext, + ): Promise { + if (!didUrl.includes('#')) { + return didDoc; + } + + // Get the DID component by controllerKeyID (e.g. didUrl is 'did:example:123#controllerKeyID') + const didComponent: any = await context.agent.getDIDComponentById({ + didDocument: didDoc, + didUrl, + }); + + didComponent['@context'] = didDoc['@context']; + return didComponent; // return the DID component (Verification Method) + } +} diff --git a/packages/credential-data-integrity/tsconfig.json b/packages/credential-data-integrity/tsconfig.json new file mode 100644 index 00000000..eaada0db --- /dev/null +++ b/packages/credential-data-integrity/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../tsconfig.settings.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build", + "declarationDir": "build", + "resolveJsonModule": true + }, + "references": [ + { + "path": "../core-types" + } + ], + "include": [ + "./**/*.ts", + "./src/plugin.schema.json", + "src/**/*.json", + ] +} \ No newline at end of file diff --git a/packages/credential-data-integrity/web-did-doc-router-example.md b/packages/credential-data-integrity/web-did-doc-router-example.md new file mode 100644 index 00000000..70bf5bb7 --- /dev/null +++ b/packages/credential-data-integrity/web-did-doc-router-example.md @@ -0,0 +1,323 @@ +# Web DID Document Router Example For Credential Data Integrity + +If you want to try using the `credential-data-integrity` plugin with a `did:web` DID, you can use the following example to create a Web DID Document router. + +This example will use the `Multikey` key type for Data Integrity proof. + +You need to replace entire content of your [web-did-doc-router.ts](../remote-server/src/web-did-doc-router.ts) file with the following code and then rebuild the `remote-server` package. + +```ts +// web-did-doc-router.ts + +import { + IIdentifier, + IDIDManager, + TAgent, + TKeyType, + IKey, +} from '@vckit/core-types'; +import { bytesToBase58, bytesToMultibase, hexToBytes } from '@veramo/utils'; +import { Request, Router } from 'express'; +import { ServiceEndpoint, VerificationMethod } from 'did-resolver'; +import { Ed25519KeyPair, JsonWebKey2020 } from '@transmute/ed25519-key-pair'; + +interface RequestWithAgentDIDManager extends Request { + agent?: TAgent; +} + +/** + * The URL path to the DID document, used by did:web when the identifier is a hostname. + * + * @public + */ +export const didDocEndpoint = '/.well-known/did.json'; + +const defaultKeyMapping: Record = { + Secp256k1: ['EcdsaSecp256k1VerificationKey2019'], + Secp256r1: ['EcdsaSecp256r1VerificationKey2019'], + Ed25519: ['Multikey'], + X25519: ['X25519KeyAgreementKey2019'], + Bls12381G1: ['Bls12381G1Key2020'], + Bls12381G2: ['Bls12381G2Key2020'], +}; + +/** + * @public + */ +export interface WebDidDocRouterOptions { + services?: ServiceEndpoint[]; + keyMapping?: Record; +} + +/** + * Creates a router that serves `did:web` DID Documents + * + * @param options - Initialization option + * @returns Expressjs router + * + * @public + */ +export const WebDidDocRouter = (options: WebDidDocRouterOptions): Router => { + const router = Router(); + + const webKeysForIdentifier = async (identifier: IIdentifier) => { + const key = identifier.keys.find((k) => k.type === 'Ed25519'); + if (!key) { + throw new Error('JsonWebKey2020 requires Ed25519 key'); + } + // key.kid = + // 'bfd193662cf9ab7c7eabde2c14611bdb4aea266db20d1f13e4c1fc02159511c2'; + // key.publicKeyHex = + // 'bfd193662cf9ab7c7eabde2c14611bdb4aea266db20d1f13e4c1fc02159511c2'; + const publicKeyBytes = hexToBytes(key.publicKeyHex); + const publicKeyMultibase = bytesToMultibase( + hexToBytes(key.publicKeyHex), + 'Ed25519' + ); + + const controller = `did:key:${publicKeyMultibase}`; + const id = `${controller}#${publicKeyMultibase}`; + + const ed25519KeyPair = new Ed25519KeyPair({ + id: id, + type: 'JsonWebKey2020', + controller: controller, + publicKey: publicKeyBytes, + }); + + const keys = await Promise.all( + [ + ...((await ed25519KeyPair.getDerivedKeyPairs()) as Ed25519KeyPair[]), + ].map(async (k: Ed25519KeyPair, i: number) => { + const wk = await k.export({ + type: 'JsonWebKey2020', + privateKey: false, + }); + wk.id = `${identifier.did}#${key.kid}-key-${i}`; + wk.controller = identifier.did; + return wk; + }) + ); + return keys as JsonWebKey2020[]; + }; + + const didDocForIdentifier = async (identifier: IIdentifier) => { + const contexts = new Set(); + + const allKeys: (VerificationMethod | VerificationMethod[])[] = + await Promise.all( + identifier.keys.flatMap(async (key: IKey) => { + const supportedTypes = defaultKeyMapping[key.type] || [key.type]; // Get supported types for this key + const verificationMethods: VerificationMethod[] = []; + + for (const type of supportedTypes) { + const vm: VerificationMethod = { + id: `${identifier.did}#${key.kid}`, + type, + controller: identifier.did, + publicKeyHex: key.publicKeyHex, + }; + + switch (type) { + case 'EcdsaSecp256k1VerificationKey2019': + case 'EcdsaSecp256k1RecoveryMethod2020': + contexts.add('https://w3id.org/security/v2'); + contexts.add( + 'https://w3id.org/security/suites/secp256k1recovery-2020/v2', + ); + verificationMethods.push(vm); + break; + + case 'Ed25519VerificationKey2018': + contexts.add( + 'https://w3id.org/security/suites/ed25519-2018/v1', + ); + vm.publicKeyBase58 = bytesToBase58( + hexToBytes(key.publicKeyHex), + ); + delete vm.publicKeyHex; + verificationMethods.push(vm); + break; + + case 'X25519KeyAgreementKey2019': + contexts.add('https://w3id.org/security/suites/x25519-2019/v1'); + vm.publicKeyBase58 = bytesToBase58( + hexToBytes(key.publicKeyHex), + ); + delete vm.publicKeyHex; + verificationMethods.push(vm); + break; + + case 'Ed25519VerificationKey2020': + contexts.add( + 'https://w3id.org/security/suites/ed25519-2020/v1', + ); + vm.publicKeyMultibase = bytesToMultibase( + hexToBytes(key.publicKeyHex), + 'Ed25519', + ); + delete vm.publicKeyHex; + verificationMethods.push(vm); + break; + + case 'X25519KeyAgreementKey2020': + contexts.add('https://w3id.org/security/suites/x25519-2020/v1'); + vm.publicKeyMultibase = bytesToMultibase( + hexToBytes(key.publicKeyHex), + 'Ed25519', + ); + delete vm.publicKeyHex; + verificationMethods.push(vm); + break; + + case 'EcdsaSecp256r1VerificationKey2019': + contexts.add('https://w3id.org/security/v2'); + verificationMethods.push(vm); + break; + + case 'Bls12381G1Key2020': + case 'Bls12381G2Key2020': + contexts.add('https://w3id.org/security/bbs/v1'); + verificationMethods.push(vm); + break; + + case 'JsonWebKey2020': + const webKeys = (await webKeysForIdentifier(identifier)).map( + (k, i) => ({ + id: `${identifier.did}#${key.kid}-${type}-key-${i}`, + type: type, + controller: k.controller, + publicKeyJwk: k.publicKeyJwk, + }), + ); + + if (webKeys.length > 0) { + contexts.add('https://w3id.org/security/suites/jws-2020/v1'); + } + + verificationMethods.push(...webKeys); + break; + + case 'Multikey': + // This context is required for the Multikey verification type + contexts.add('https://w3id.org/security/multikey/v1'); + + /** + * The public key multibase must consist of a binary value that starts with the two-byte prefix '0xed01' + * For details see: https://www.w3.org/TR/vc-di-eddsa/#multikey + */ + const MULTICODEC_PUB_HEADER = new Uint8Array([0xed, 0x01]); + const identifierWebKeys = ( + await webKeysForIdentifier(identifier) + ).map((k, i) => ({ + id: `${identifier.did}#${key.kid}-${type}-key-${i}`, + type: type, + controller: k.controller, + publicKeyJwk: k.publicKeyJwk, + publicKeyMultibase: `z${bytesToBase58( + new Uint8Array([ + ...MULTICODEC_PUB_HEADER, + ...hexToBytes(key.publicKeyHex), + ]), + )}`, + publicKeyBase58: bytesToBase58(hexToBytes(key.publicKeyHex)), + })); + + verificationMethods.push(...identifierWebKeys); + break; + + default: + verificationMethods.push(vm); + break; + } + } + + return verificationMethods; + }) + ); + + const flattenAllKeys: VerificationMethod[] = allKeys.flat(); + + // ed25519 keys can also be converted to x25519 for key agreement + const keyAgreementKeyIds = flattenAllKeys + .filter((key) => + ['Ed25519VerificationKey2018', 'X25519KeyAgreementKey2019'].includes( + key.type + ) + ) + .map((key) => key.id); + const signingKeyIds = flattenAllKeys + .filter((key) => key.type !== 'X25519KeyAgreementKey2019') + .map((key) => key.id); + + const didDoc = { + '@context': ['https://www.w3.org/ns/did/v1', ...contexts], + id: identifier.did, + verificationMethod: flattenAllKeys, + authentication: signingKeyIds, + assertionMethod: signingKeyIds, + keyAgreement: keyAgreementKeyIds, + service: [...(options?.services || []), ...(identifier?.services || [])], + }; + + return didDoc; + }; + + const getAliasForRequest = (req: Request) => { + return encodeURIComponent(req.get('host') || req.hostname); + }; + + router.get(didDocEndpoint, async (req: RequestWithAgentDIDManager, res) => { + if (req.agent) { + try { + const serverIdentifier = await req.agent.didManagerGet({ + did: 'did:web:' + getAliasForRequest(req), + }); + const didDoc = await didDocForIdentifier(serverIdentifier); + res.json(didDoc); + } catch (e) { + res.status(404).send(e); + } + } + }); + + router.get( + /^\/(.+)\/did.json$/, + async (req: RequestWithAgentDIDManager, res) => { + if (req.agent) { + try { + const identifier = await req.agent.didManagerGet({ + did: + 'did:web:' + + getAliasForRequest(req) + + ':' + + req.params[0].replace(/\//g, ':'), + }); + const didDoc = await didDocForIdentifier(identifier); + res.json(didDoc); + } catch (e) { + res.status(404).send(e); + } + } + } + ); + + router.get('/keys/:did', async (req: RequestWithAgentDIDManager, res) => { + if (req.agent) { + try { + console.log(req.params); + const identifier = await req.agent.didManagerGet({ + did: req.params.did, + }); + const keys = await webKeysForIdentifier(identifier); + res.json(keys); + } catch (e) { + res.status(404).send(e); + } + } + }); + + return router; +}; + +``` \ No newline at end of file