From 8e03f35f8e1cd02dac4df02d1f80f2c5a921dfef Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 5 Jan 2022 14:04:09 +0100 Subject: [PATCH] feat: add generic did resolver (#554) * feat: did:key, did:web, did:sov resolver Signed-off-by: Timo Glastra --- packages/core/package.json | 10 +- packages/core/src/agent/Agent.ts | 19 +- .../modules/credentials/CredentialUtils.ts | 5 +- packages/core/src/modules/dids/DidsModule.ts | 18 ++ .../dids/__tests__/DidKeyBls12381G1.test.ts | 63 ++++ .../dids/__tests__/DidKeyBls12381G1G2.test.ts | 100 +++++++ .../dids/__tests__/DidKeyBls12381G2.test.ts | 65 +++++ .../dids/__tests__/DidKeyEd25519.test.ts | 63 ++++ .../dids/__tests__/DidKeyX25519.test.ts | 63 ++++ .../dids/__tests__/DidResolverService.test.ts | 67 +++++ .../dids/__tests__/IndyDidResolver.test.ts | 111 +++++++ .../dids/__tests__/KeyDidResolver.test.ts | 55 ++++ .../__tests__/__fixtures__/didExample123.json | 94 ++++++ .../__fixtures__/didExample456Invalid.json | 86 ++++++ .../__fixtures__/didKeyBls12381g1.json | 28 ++ .../__fixtures__/didKeyBls12381g1g2.json | 38 +++ .../__fixtures__/didKeyBls12381g2.json | 28 ++ .../__tests__/__fixtures__/didKeyEd25519.json | 40 +++ .../__tests__/__fixtures__/didKeyX25519.json | 20 ++ .../didSovR1xKJw17sUoXhejEpugMYJ.json | 46 +++ .../didSovWJz9mHyW9BZksioQnRsrAo.json | 53 ++++ .../src/modules/dids/domain/DidDocument.ts | 130 +++++++++ .../modules/dids/domain/DidDocumentBuilder.ts | 93 ++++++ .../core/src/modules/dids/domain/DidKey.ts | 220 ++++++++++++++ .../dids/domain/__tests__/DidDocument.test.ts | 275 ++++++++++++++++++ .../core/src/modules/dids/domain/index.ts | 4 + .../dids/domain/service/DidCommService.ts | 39 +++ .../dids/domain/service/DidCommV2Service.ts | 24 ++ .../dids/domain/service/DidDocumentService.ts | 24 ++ .../dids/domain/service/IndyAgentService.ts | 33 +++ .../dids/domain/service/ServiceTransformer.ts | 40 +++ .../src/modules/dids/domain/service/index.ts | 7 + .../verificationMethod/VerificationMethod.ts | 73 +++++ .../VerificationMethodTransformer.ts | 59 ++++ .../dids/domain/verificationMethod/index.ts | 4 + packages/core/src/modules/dids/index.ts | 3 + packages/core/src/modules/dids/parse.ts | 7 + .../src/modules/dids/resolvers/DidResolver.ts | 6 + .../modules/dids/resolvers/IndyDidResolver.ts | 134 +++++++++ .../modules/dids/resolvers/KeyDidResolver.ts | 31 ++ .../modules/dids/resolvers/WebDidResolver.ts | 40 +++ .../dids/services/DidResolverService.ts | 56 ++++ .../core/src/modules/dids/services/index.ts | 1 + packages/core/src/modules/dids/types.ts | 13 + packages/core/src/modules/ledger/IndyPool.ts | 8 +- .../ledger/services/IndyLedgerService.ts | 36 +++ .../core/src/storage/IndyStorageService.ts | 5 +- packages/core/src/utils/HashlinkEncoder.ts | 4 +- packages/core/src/utils/did.ts | 19 ++ packages/core/tests/dids.test.ts | 113 +++++++ packages/core/tests/helpers.ts | 8 + packages/react-native/package.json | 2 +- yarn.lock | 97 +++++- 53 files changed, 2643 insertions(+), 37 deletions(-) create mode 100644 packages/core/src/modules/dids/DidsModule.ts create mode 100644 packages/core/src/modules/dids/__tests__/DidKeyBls12381G1.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/DidKeyBls12381G1G2.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/DidKeyBls12381G2.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/DidKeyEd25519.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/DidKeyX25519.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/DidResolverService.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/IndyDidResolver.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/KeyDidResolver.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didExample123.json create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didExample456Invalid.json create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json create mode 100644 packages/core/src/modules/dids/domain/DidDocument.ts create mode 100644 packages/core/src/modules/dids/domain/DidDocumentBuilder.ts create mode 100644 packages/core/src/modules/dids/domain/DidKey.ts create mode 100644 packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts create mode 100644 packages/core/src/modules/dids/domain/index.ts create mode 100644 packages/core/src/modules/dids/domain/service/DidCommService.ts create mode 100644 packages/core/src/modules/dids/domain/service/DidCommV2Service.ts create mode 100644 packages/core/src/modules/dids/domain/service/DidDocumentService.ts create mode 100644 packages/core/src/modules/dids/domain/service/IndyAgentService.ts create mode 100644 packages/core/src/modules/dids/domain/service/ServiceTransformer.ts create mode 100644 packages/core/src/modules/dids/domain/service/index.ts create mode 100644 packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts create mode 100644 packages/core/src/modules/dids/domain/verificationMethod/VerificationMethodTransformer.ts create mode 100644 packages/core/src/modules/dids/domain/verificationMethod/index.ts create mode 100644 packages/core/src/modules/dids/index.ts create mode 100644 packages/core/src/modules/dids/parse.ts create mode 100644 packages/core/src/modules/dids/resolvers/DidResolver.ts create mode 100644 packages/core/src/modules/dids/resolvers/IndyDidResolver.ts create mode 100644 packages/core/src/modules/dids/resolvers/KeyDidResolver.ts create mode 100644 packages/core/src/modules/dids/resolvers/WebDidResolver.ts create mode 100644 packages/core/src/modules/dids/services/DidResolverService.ts create mode 100644 packages/core/src/modules/dids/services/index.ts create mode 100644 packages/core/src/modules/dids/types.ts create mode 100644 packages/core/tests/dids.test.ts diff --git a/packages/core/package.json b/packages/core/package.json index 2f958fa5f5..8a8a83fce8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -24,7 +24,9 @@ }, "dependencies": { "@multiformats/base-x": "^4.0.1", - "@types/indy-sdk": "^1.16.6", + "@stablelib/ed25519": "^1.0.2", + "@stablelib/sha256": "^1.0.1", + "@types/indy-sdk": "^1.16.8", "@types/node-fetch": "^2.5.10", "@types/ws": "^7.4.4", "abort-controller": "^3.0.0", @@ -33,18 +35,20 @@ "buffer": "^6.0.3", "class-transformer": "0.5.1", "class-validator": "0.13.1", - "js-sha256": "^0.9.0", + "did-resolver": "^3.1.3", "lru_map": "^0.4.1", "luxon": "^1.27.0", "make-error": "^1.3.6", "multibase": "^4.0.4", + "multiformats": "^9.4.14", "multihashes": "^4.0.2", "object-inspect": "^1.10.3", "query-string": "^7.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.1.0", "tsyringe": "^4.5.0", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "web-did-resolver": "^2.0.8" }, "devDependencies": { "@types/bn.js": "^5.1.0", diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 5fa60d0055..7981dbf8b4 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -17,6 +17,7 @@ import { AriesFrameworkError } from '../error' import { BasicMessagesModule } from '../modules/basic-messages/BasicMessagesModule' import { ConnectionsModule } from '../modules/connections/ConnectionsModule' import { CredentialsModule } from '../modules/credentials/CredentialsModule' +import { DidsModule } from '../modules/dids/DidsModule' import { DiscoverFeaturesModule } from '../modules/discover-features' import { LedgerModule } from '../modules/ledger/LedgerModule' import { ProofsModule } from '../modules/proofs/ProofsModule' @@ -45,14 +46,15 @@ export class Agent { private _isInitialized = false public messageSubscription: Subscription - public readonly connections!: ConnectionsModule - public readonly proofs!: ProofsModule - public readonly basicMessages!: BasicMessagesModule - public readonly ledger!: LedgerModule - public readonly credentials!: CredentialsModule - public readonly mediationRecipient!: RecipientModule - public readonly mediator!: MediatorModule - public readonly discovery!: DiscoverFeaturesModule + public readonly connections: ConnectionsModule + public readonly proofs: ProofsModule + public readonly basicMessages: BasicMessagesModule + public readonly ledger: LedgerModule + public readonly credentials: CredentialsModule + public readonly mediationRecipient: RecipientModule + public readonly mediator: MediatorModule + public readonly discovery: DiscoverFeaturesModule + public readonly dids: DidsModule public readonly wallet: Wallet public constructor(initialConfig: InitConfig, dependencies: AgentDependencies) { @@ -102,6 +104,7 @@ export class Agent { this.basicMessages = this.container.resolve(BasicMessagesModule) this.ledger = this.container.resolve(LedgerModule) this.discovery = this.container.resolve(DiscoverFeaturesModule) + this.dids = this.container.resolve(DidsModule) // Listen for new messages (either from transports or somewhere else in the framework / extensions) this.messageSubscription = this.eventEmitter diff --git a/packages/core/src/modules/credentials/CredentialUtils.ts b/packages/core/src/modules/credentials/CredentialUtils.ts index 95d9b4c2be..52bdb6ccc2 100644 --- a/packages/core/src/modules/credentials/CredentialUtils.ts +++ b/packages/core/src/modules/credentials/CredentialUtils.ts @@ -1,11 +1,12 @@ import type { LinkedAttachment } from '../../utils/LinkedAttachment' import type { CredValues } from 'indy-sdk' +import { hash as sha256 } from '@stablelib/sha256' import BigNumber from 'bn.js' -import { sha256 } from 'js-sha256' import { AriesFrameworkError } from '../../error/AriesFrameworkError' import { encodeAttachment } from '../../utils/attachment' +import { Buffer } from '../../utils/buffer' import { isBoolean, isNumber, isString } from '../../utils/type' import { CredentialPreview, CredentialPreviewAttribute } from './messages/CredentialPreview' @@ -164,7 +165,7 @@ export class CredentialUtils { value = 'None' } - return new BigNumber(sha256.array(value as string)).toString() + return new BigNumber(sha256(Buffer.from(value as string))).toString() } private static isInt32(number: number) { diff --git a/packages/core/src/modules/dids/DidsModule.ts b/packages/core/src/modules/dids/DidsModule.ts new file mode 100644 index 0000000000..10e9b9810a --- /dev/null +++ b/packages/core/src/modules/dids/DidsModule.ts @@ -0,0 +1,18 @@ +import type { DidResolutionOptions } from './types' + +import { Lifecycle, scoped } from 'tsyringe' + +import { DidResolverService } from './services/DidResolverService' + +@scoped(Lifecycle.ContainerScoped) +export class DidsModule { + private resolverService: DidResolverService + + public constructor(resolverService: DidResolverService) { + this.resolverService = resolverService + } + + public resolve(didUrl: string, options?: DidResolutionOptions) { + return this.resolverService.resolve(didUrl, options) + } +} diff --git a/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1.test.ts b/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1.test.ts new file mode 100644 index 0000000000..27640470ca --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1.test.ts @@ -0,0 +1,63 @@ +import { BufferEncoder } from '../../../utils/BufferEncoder' +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { Buffer } from '../../../utils/buffer' +import { DidKey, KeyType } from '../domain/DidKey' + +import didKeyBls12381g1Fixture from './__fixtures__/didKeyBls12381g1.json' + +const TEST_BLS12381G1_BASE58_KEY = '6FywSzB5BPd7xehCo1G4nYHAoZPMMP3gd4PLnvgA6SsTsogtz8K7RDznqLpFPLZXAE' +const TEST_BLS12381G1_FINGERPRINT = 'z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA' +const TEST_BLS12381G1_DID = `did:key:${TEST_BLS12381G1_FINGERPRINT}` +const TEST_BLS12381G1_KEY_ID = `${TEST_BLS12381G1_DID}#${TEST_BLS12381G1_FINGERPRINT}` +const TEST_BLS12381G1_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([234, 1]), + BufferEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY), +]) + +describe('DidKey', () => { + describe('bls12381g1', () => { + it('creates a DidKey instance from public key bytes and bls12381g1 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY) + + const didKey = DidKey.fromPublicKey(publicKeyBytes, KeyType.BLS12381G1) + + expect(didKey.did).toBe(TEST_BLS12381G1_DID) + }) + + it('creates a DidKey instance from a base58 encoded public key and bls12381g1 key type', async () => { + const didKey = DidKey.fromPublicKeyBase58(TEST_BLS12381G1_BASE58_KEY, KeyType.BLS12381G1) + + expect(didKey.did).toBe(TEST_BLS12381G1_DID) + }) + + it('creates a DidKey instance from a fingerprint', async () => { + const didKey = DidKey.fromFingerprint(TEST_BLS12381G1_FINGERPRINT) + + expect(didKey.did).toBe(TEST_BLS12381G1_DID) + }) + + it('creates a DidKey instance from a did', async () => { + const didKey = DidKey.fromDid(TEST_BLS12381G1_DID) + + expect(didKey.publicKeyBase58).toBe(TEST_BLS12381G1_BASE58_KEY) + }) + + it('should correctly calculate the getter properties', async () => { + const didKey = DidKey.fromDid(TEST_BLS12381G1_DID) + + expect(didKey.fingerprint).toBe(TEST_BLS12381G1_FINGERPRINT) + expect(didKey.did).toBe(TEST_BLS12381G1_DID) + expect(didKey.publicKeyBase58).toBe(TEST_BLS12381G1_BASE58_KEY) + expect(didKey.publicKey).toEqual(BufferEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY)) + expect(didKey.keyType).toBe(KeyType.BLS12381G1) + expect(didKey.keyId).toBe(TEST_BLS12381G1_KEY_ID) + expect(didKey.prefixedPublicKey.equals(TEST_BLS12381G1_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const didKey = DidKey.fromDid(TEST_BLS12381G1_DID) + + expect(JsonTransformer.toJSON(didKey.didDocument)).toMatchObject(didKeyBls12381g1Fixture) + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1G2.test.ts b/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1G2.test.ts new file mode 100644 index 0000000000..b094d2aa6a --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1G2.test.ts @@ -0,0 +1,100 @@ +import { BufferEncoder } from '../../../utils/BufferEncoder' +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { Buffer } from '../../../utils/buffer' +import { DidKey, KeyType } from '../domain/DidKey' + +import didKeyBls12381g1g2Fixture from './__fixtures__/didKeyBls12381g1g2.json' + +const TEST_BLS12381G1G2_BASE58_KEY = + 'AQ4MiG1JKHmM5N4CgkF9uQ484PHN7gXB3ctF4ayL8hT6FdD6rcfFS3ZnMNntYsyJBckfNPf3HL8VU8jzgyT3qX88Yg3TeF2NkG2aZnJDNnXH1jkJStWMxjLw22LdphqAj1rSorsDhHjE8Rtz61bD6FP9aPokQUDVpZ4zXqsXVcxJ7YEc66TTLTTPwQPS7uNM4u2Fs' +const TEST_BLS12381G1G2_FINGERPRINT = + 'z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s' +const TEST_BLS12381G1G2_DID = `did:key:${TEST_BLS12381G1G2_FINGERPRINT}` + +const TEST_BLS12381G1_BASE58_KEY = '7BVES4h78wzabPAfMhchXyH5d8EX78S5TtzePH2YkftWcE6by9yj3NTAv9nsyCeYch' +const TEST_BLS12381G1_FINGERPRINT = 'z3tEG5qmJZX29jJSX5kyhDR5YJNnefJFdwTxRqk6zbEPv4Pf2xF12BpmXv9NExxSRFGfxd' +const TEST_BLS12381G1_DID = `did:key:${TEST_BLS12381G1_FINGERPRINT}` + +const TEST_BLS12381G2_BASE58_KEY = + '26d2BdqELsXg7ZHCWKL2D5Y2S7mYrpkdhJemSEEvokd4qy4TULJeeU44hYPGKo4x4DbBp5ARzkv1D6xuB3bmhpdpKAXuXtode67wzh9PCtW8kTqQhH19VSiFZkLNkhe9rtf3' +const TEST_BLS12381G2_FINGERPRINT = + 'zUC7LTa4hWtaE9YKyDsMVGiRNqPMN3s4rjBdB3MFi6PcVWReNfR72y3oGW2NhNcaKNVhMobh7aHp8oZB3qdJCs7RebM2xsodrSm8MmePbN25NTGcpjkJMwKbcWfYDX7eHCJjPGM' +const TEST_BLS12381G2_DID = `did:key:${TEST_BLS12381G2_FINGERPRINT}` + +const TEST_BLS12381G1G2_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([238, 1]), + BufferEncoder.fromBase58(TEST_BLS12381G1G2_BASE58_KEY), +]) + +describe('DidKey', () => { + describe('bls12381g1g2', () => { + it('creates a DidKey instance from public key bytes and bls12381g1g2 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_BLS12381G1G2_BASE58_KEY) + + const didKey = DidKey.fromPublicKey(publicKeyBytes, KeyType.BLS12381G1G2) + + expect(didKey.did).toBe(TEST_BLS12381G1G2_DID) + }) + + it('creates a DidKey instance from a base58 encoded public key and bls12381g1g2 key type', async () => { + const didKey = DidKey.fromPublicKeyBase58(TEST_BLS12381G1G2_BASE58_KEY, KeyType.BLS12381G1G2) + + expect(didKey.did).toBe(TEST_BLS12381G1G2_DID) + }) + + it('creates a DidKey instance from a fingerprint', async () => { + const didKey = DidKey.fromFingerprint(TEST_BLS12381G1G2_FINGERPRINT) + + expect(didKey.did).toBe(TEST_BLS12381G1G2_DID) + }) + + it('creates a DidKey instance from a did', async () => { + const didKey = DidKey.fromDid(TEST_BLS12381G1G2_DID) + + expect(didKey.publicKeyBase58).toBe(TEST_BLS12381G1G2_BASE58_KEY) + }) + + it('should correctly calculate the getter properties', async () => { + const didKey = DidKey.fromDid(TEST_BLS12381G1G2_DID) + + expect(didKey.fingerprint).toBe(TEST_BLS12381G1G2_FINGERPRINT) + expect(didKey.did).toBe(TEST_BLS12381G1G2_DID) + expect(didKey.publicKeyBase58).toBe(TEST_BLS12381G1G2_BASE58_KEY) + expect(didKey.publicKey).toEqual(BufferEncoder.fromBase58(TEST_BLS12381G1G2_BASE58_KEY)) + expect(didKey.keyType).toBe(KeyType.BLS12381G1G2) + expect(didKey.prefixedPublicKey.equals(TEST_BLS12381G1G2_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const didKey = DidKey.fromDid(TEST_BLS12381G1G2_DID) + + expect(JsonTransformer.toJSON(didKey.didDocument)).toMatchObject(didKeyBls12381g1g2Fixture) + }) + + it('should correctly go from g1g2 to g1', async () => { + const g1g2DidKey = DidKey.fromDid(TEST_BLS12381G1G2_DID) + + const g1PublicKey = g1g2DidKey.publicKey.slice(0, 48) + const g1DidKey = DidKey.fromPublicKey(g1PublicKey, KeyType.BLS12381G1) + + expect(g1DidKey.fingerprint).toBe(TEST_BLS12381G1_FINGERPRINT) + expect(g1DidKey.did).toBe(TEST_BLS12381G1_DID) + expect(g1DidKey.publicKeyBase58).toBe(TEST_BLS12381G1_BASE58_KEY) + expect(g1DidKey.publicKey).toEqual(BufferEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY)) + expect(g1DidKey.keyType).toBe(KeyType.BLS12381G1) + }) + + it('should correctly go from g1g2 to g2', async () => { + const g1g2DidKey = DidKey.fromDid(TEST_BLS12381G1G2_DID) + + const g2PublicKey = g1g2DidKey.publicKey.slice(48) + const g2DidKey = DidKey.fromPublicKey(g2PublicKey, KeyType.BLS12381G2) + + expect(g2DidKey.fingerprint).toBe(TEST_BLS12381G2_FINGERPRINT) + expect(g2DidKey.did).toBe(TEST_BLS12381G2_DID) + expect(g2DidKey.publicKeyBase58).toBe(TEST_BLS12381G2_BASE58_KEY) + expect(g2DidKey.publicKey).toEqual(BufferEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY)) + expect(g2DidKey.keyType).toBe(KeyType.BLS12381G2) + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/DidKeyBls12381G2.test.ts b/packages/core/src/modules/dids/__tests__/DidKeyBls12381G2.test.ts new file mode 100644 index 0000000000..7daedceea4 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/DidKeyBls12381G2.test.ts @@ -0,0 +1,65 @@ +import { BufferEncoder } from '../../../utils/BufferEncoder' +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { Buffer } from '../../../utils/buffer' +import { DidKey, KeyType } from '../domain/DidKey' + +import didKeyBls12381g2Fixture from './__fixtures__/didKeyBls12381g2.json' + +const TEST_BLS12381G2_BASE58_KEY = + 'mxE4sHTpbPcmxNviRVR9r7D2taXcNyVJmf9TBUFS1gRt3j3Ej9Seo59GQeCzYwbQgDrfWCwEJvmBwjLvheAky5N2NqFVzk4kuq3S8g4Fmekai4P622vHqWjFrsioYYDqhf9' +const TEST_BLS12381G2_FINGERPRINT = + 'zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT' +const TEST_BLS12381G2_DID = `did:key:${TEST_BLS12381G2_FINGERPRINT}` +const TEST_BLS12381G2_KEY_ID = `${TEST_BLS12381G2_DID}#${TEST_BLS12381G2_FINGERPRINT}` +const TEST_BLS12381G2_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([235, 1]), + BufferEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY), +]) + +describe('DidKey', () => { + describe('bls12381g2', () => { + it('creates a DidKey instance from public key bytes and bls12381g2 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY) + + const didKey = DidKey.fromPublicKey(publicKeyBytes, KeyType.BLS12381G2) + + expect(didKey.did).toBe(TEST_BLS12381G2_DID) + }) + + it('creates a DidKey instance from a base58 encoded public key and bls12381g2 key type', async () => { + const didKey = DidKey.fromPublicKeyBase58(TEST_BLS12381G2_BASE58_KEY, KeyType.BLS12381G2) + + expect(didKey.did).toBe(TEST_BLS12381G2_DID) + }) + + it('creates a DidKey instance from a fingerprint', async () => { + const didKey = DidKey.fromFingerprint(TEST_BLS12381G2_FINGERPRINT) + + expect(didKey.did).toBe(TEST_BLS12381G2_DID) + }) + + it('creates a DidKey instance from a did', async () => { + const didKey = DidKey.fromDid(TEST_BLS12381G2_DID) + + expect(didKey.publicKeyBase58).toBe(TEST_BLS12381G2_BASE58_KEY) + }) + + it('should correctly calculate the getter properties', async () => { + const didKey = DidKey.fromDid(TEST_BLS12381G2_DID) + + expect(didKey.fingerprint).toBe(TEST_BLS12381G2_FINGERPRINT) + expect(didKey.did).toBe(TEST_BLS12381G2_DID) + expect(didKey.publicKeyBase58).toBe(TEST_BLS12381G2_BASE58_KEY) + expect(didKey.publicKey).toEqual(BufferEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY)) + expect(didKey.keyType).toBe(KeyType.BLS12381G2) + expect(didKey.keyId).toBe(TEST_BLS12381G2_KEY_ID) + expect(didKey.prefixedPublicKey.equals(TEST_BLS12381G2_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const didKey = DidKey.fromDid(TEST_BLS12381G2_DID) + + expect(JsonTransformer.toJSON(didKey.didDocument)).toMatchObject(didKeyBls12381g2Fixture) + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/DidKeyEd25519.test.ts b/packages/core/src/modules/dids/__tests__/DidKeyEd25519.test.ts new file mode 100644 index 0000000000..c8baf3f9e7 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/DidKeyEd25519.test.ts @@ -0,0 +1,63 @@ +import { BufferEncoder } from '../../../utils/BufferEncoder' +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { Buffer } from '../../../utils/buffer' +import { DidKey, KeyType } from '../domain/DidKey' + +import didKeyEd25519Fixture from './__fixtures__/didKeyEd25519.json' + +const TEST_ED25519_BASE58_KEY = '8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K' +const TEST_ED25519_FINGERPRINT = 'z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th' +const TEST_ED25519_DID = `did:key:${TEST_ED25519_FINGERPRINT}` +const TEST_ED25519_KEY_ID = `${TEST_ED25519_DID}#${TEST_ED25519_FINGERPRINT}` +const TEST_ED25519_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([237, 1]), + BufferEncoder.fromBase58(TEST_ED25519_BASE58_KEY), +]) + +describe('DidKey', () => { + describe('ed25519', () => { + it('creates a DidKey instance from public key bytes and ed25519 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_ED25519_BASE58_KEY) + + const didKey = DidKey.fromPublicKey(publicKeyBytes, KeyType.ED25519) + + expect(didKey.did).toBe(TEST_ED25519_DID) + }) + + it('creates a DidKey instance from a base58 encoded public key and ed25519 key type', async () => { + const didKey = DidKey.fromPublicKeyBase58(TEST_ED25519_BASE58_KEY, KeyType.ED25519) + + expect(didKey.did).toBe(TEST_ED25519_DID) + }) + + it('creates a DidKey instance from a fingerprint', async () => { + const didKey = DidKey.fromFingerprint(TEST_ED25519_FINGERPRINT) + + expect(didKey.did).toBe(TEST_ED25519_DID) + }) + + it('creates a DidKey instance from a did', async () => { + const didKey = DidKey.fromDid(TEST_ED25519_DID) + + expect(didKey.publicKeyBase58).toBe(TEST_ED25519_BASE58_KEY) + }) + + it('should correctly calculate the getter properties', async () => { + const didKey = DidKey.fromDid(TEST_ED25519_DID) + + expect(didKey.fingerprint).toBe(TEST_ED25519_FINGERPRINT) + expect(didKey.did).toBe(TEST_ED25519_DID) + expect(didKey.publicKeyBase58).toBe(TEST_ED25519_BASE58_KEY) + expect(didKey.publicKey).toEqual(BufferEncoder.fromBase58(TEST_ED25519_BASE58_KEY)) + expect(didKey.keyType).toBe(KeyType.ED25519) + expect(didKey.keyId).toBe(TEST_ED25519_KEY_ID) + expect(didKey.prefixedPublicKey.equals(TEST_ED25519_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const didKey = DidKey.fromDid(TEST_ED25519_DID) + + expect(JsonTransformer.toJSON(didKey.didDocument)).toMatchObject(didKeyEd25519Fixture) + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/DidKeyX25519.test.ts b/packages/core/src/modules/dids/__tests__/DidKeyX25519.test.ts new file mode 100644 index 0000000000..1a5150fd73 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/DidKeyX25519.test.ts @@ -0,0 +1,63 @@ +import { BufferEncoder } from '../../../utils/BufferEncoder' +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { Buffer } from '../../../utils/buffer' +import { DidKey, KeyType } from '../domain/DidKey' + +import didKeyX25519Fixture from './__fixtures__/didKeyX25519.json' + +const TEST_X25519_BASE58_KEY = '6fUMuABnqSDsaGKojbUF3P7ZkEL3wi2njsDdUWZGNgCU' +const TEST_X25519_FINGERPRINT = 'z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE' +const TEST_X25519_DID = `did:key:${TEST_X25519_FINGERPRINT}` +const TEST_X25519_KEY_ID = `${TEST_X25519_DID}#${TEST_X25519_FINGERPRINT}` +const TEST_X25519_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([236, 1]), + BufferEncoder.fromBase58(TEST_X25519_BASE58_KEY), +]) + +describe('DidKey', () => { + describe('x25519', () => { + it('creates a DidKey instance from public key bytes and x25519 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_X25519_BASE58_KEY) + + const didKey = DidKey.fromPublicKey(publicKeyBytes, KeyType.X25519) + + expect(didKey.did).toBe(TEST_X25519_DID) + }) + + it('creates a DidKey instance from a base58 encoded public key and x25519 key type', async () => { + const didKey = DidKey.fromPublicKeyBase58(TEST_X25519_BASE58_KEY, KeyType.X25519) + + expect(didKey.did).toBe(TEST_X25519_DID) + }) + + it('creates a DidKey instance from a fingerprint', async () => { + const didKey = DidKey.fromFingerprint(TEST_X25519_FINGERPRINT) + + expect(didKey.did).toBe(TEST_X25519_DID) + }) + + it('creates a DidKey instance from a did', async () => { + const didKey = DidKey.fromDid(TEST_X25519_DID) + + expect(didKey.publicKeyBase58).toBe(TEST_X25519_BASE58_KEY) + }) + + it('should correctly calculate the getter properties', async () => { + const didKey = DidKey.fromDid(TEST_X25519_DID) + + expect(didKey.fingerprint).toBe(TEST_X25519_FINGERPRINT) + expect(didKey.did).toBe(TEST_X25519_DID) + expect(didKey.publicKeyBase58).toBe(TEST_X25519_BASE58_KEY) + expect(didKey.publicKey).toEqual(BufferEncoder.fromBase58(TEST_X25519_BASE58_KEY)) + expect(didKey.keyType).toBe(KeyType.X25519) + expect(didKey.keyId).toBe(TEST_X25519_KEY_ID) + expect(didKey.prefixedPublicKey.equals(TEST_X25519_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const didKey = DidKey.fromDid(TEST_X25519_DID) + + expect(JsonTransformer.toJSON(didKey.didDocument)).toMatchObject(didKeyX25519Fixture) + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts b/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts new file mode 100644 index 0000000000..4b38036dd4 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts @@ -0,0 +1,67 @@ +import type { IndyLedgerService } from '../../ledger' + +import { getAgentConfig, mockProperty } from '../../../../tests/helpers' +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { DidDocument } from '../domain' +import { parseDid } from '../parse' +import { KeyDidResolver } from '../resolvers/KeyDidResolver' +import { DidResolverService } from '../services/DidResolverService' + +import didKeyEd25519Fixture from './__fixtures__/didKeyEd25519.json' + +jest.mock('../resolvers/KeyDidResolver') + +const agentConfig = getAgentConfig('DidResolverService') + +describe('DidResolverService', () => { + const indyLedgerServiceMock = jest.fn() as unknown as IndyLedgerService + const didResolverService = new DidResolverService(agentConfig, indyLedgerServiceMock) + + it('should correctly find and call the correct resolver for a specified did', async () => { + const didKeyResolveSpy = jest.spyOn(KeyDidResolver.prototype, 'resolve') + mockProperty(KeyDidResolver.prototype, 'supportedMethods', ['key']) + + const returnValue = { + didDocument: JsonTransformer.fromJSON(didKeyEd25519Fixture, DidDocument), + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + } + didKeyResolveSpy.mockResolvedValue(returnValue) + + const result = await didResolverService.resolve('did:key:xxxx', { someKey: 'string' }) + expect(result).toEqual(returnValue) + + expect(didKeyResolveSpy).toHaveBeenCalledTimes(1) + expect(didKeyResolveSpy).toHaveBeenCalledWith('did:key:xxxx', parseDid('did:key:xxxx'), { someKey: 'string' }) + }) + + it("should return an error with 'invalidDid' if the did string couldn't be parsed", async () => { + const did = 'did:__Asd:asdfa' + + const result = await didResolverService.resolve(did) + + expect(result).toEqual({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'invalidDid', + }, + }) + }) + + it("should return an error with 'unsupportedDidMethod' if the did has no resolver", async () => { + const did = 'did:example:asdfa' + + const result = await didResolverService.resolve(did) + + expect(result).toEqual({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'unsupportedDidMethod', + }, + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/IndyDidResolver.test.ts b/packages/core/src/modules/dids/__tests__/IndyDidResolver.test.ts new file mode 100644 index 0000000000..9280b73eaf --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/IndyDidResolver.test.ts @@ -0,0 +1,111 @@ +import type { IndyEndpointAttrib } from '../../ledger/services/IndyLedgerService' +import type { GetNymResponse } from 'indy-sdk' + +import { mockFunction } from '../../../../tests/helpers' +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' +import { parseDid } from '../parse' +import { IndyDidResolver } from '../resolvers/IndyDidResolver' + +import didSovR1xKJw17sUoXhejEpugMYJFixture from './__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' +import didSovWJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json' + +jest.mock('../../ledger/services/IndyLedgerService') +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock + +const getParsed = (did: string) => { + const parsed = parseDid(did) + + if (!parsed) { + throw new Error('Could not parse') + } + + return parsed +} + +describe('DidResolver', () => { + describe('IndyDidResolver', () => { + let ledgerService: IndyLedgerService + let indyDidResolver: IndyDidResolver + + beforeEach(() => { + ledgerService = new IndyLedgerServiceMock() + indyDidResolver = new IndyDidResolver(ledgerService) + }) + + it('should correctly resolve a did:sov document', async () => { + const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' + + const nymResponse: GetNymResponse = { + did: 'R1xKJw17sUoXhejEpugMYJ', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://ssi.com', + profile: 'https://profile.com', + hub: 'https://hub.com', + } + + mockFunction(ledgerService.getPublicDid).mockResolvedValue(nymResponse) + mockFunction(ledgerService.getEndpointsForDid).mockResolvedValue(endpoints) + + const result = await indyDidResolver.resolve(did, getParsed(did)) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should resolve a did:sov document with routingKeys and types entries in the attrib', async () => { + const did = 'did:sov:WJz9mHyW9BZksioQnRsrAo' + + const nymResponse: GetNymResponse = { + did: 'WJz9mHyW9BZksioQnRsrAo', + verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://agent.com', + types: ['endpoint', 'did-communication', 'DIDComm'], + routingKeys: ['routingKey1', 'routingKey2'], + } + + mockFunction(ledgerService.getPublicDid).mockReturnValue(Promise.resolve(nymResponse)) + mockFunction(ledgerService.getEndpointsForDid).mockReturnValue(Promise.resolve(endpoints)) + + const result = await indyDidResolver.resolve(did, getParsed(did)) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { + const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' + + mockFunction(ledgerService.getPublicDid).mockRejectedValue(new Error('Error retrieving did')) + + const result = await indyDidResolver.resolve(did, getParsed(did)) + + expect(result).toMatchObject({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did 'did:sov:R1xKJw17sUoXhejEpugMYJ': Error: Error retrieving did`, + }, + }) + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/KeyDidResolver.test.ts b/packages/core/src/modules/dids/__tests__/KeyDidResolver.test.ts new file mode 100644 index 0000000000..a9a24f90a2 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/KeyDidResolver.test.ts @@ -0,0 +1,55 @@ +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { DidKey } from '../domain/DidKey' +import { KeyDidResolver } from '../resolvers/KeyDidResolver' + +import didKeyEd25519Fixture from './__fixtures__/didKeyEd25519.json' + +describe('DidResolver', () => { + describe('KeyDidResolver', () => { + let keyDidResolver: KeyDidResolver + + beforeEach(() => { + keyDidResolver = new KeyDidResolver() + }) + + it('should correctly resolve a did:key document', async () => { + const fromDidSpy = jest.spyOn(DidKey, 'fromDid') + const result = await keyDidResolver.resolve('did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th') + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didKeyEd25519Fixture, + didDocumentMetadata: {}, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + }) + expect(result.didDocument) + expect(fromDidSpy).toHaveBeenCalledTimes(1) + expect(fromDidSpy).toHaveBeenCalledWith('did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th') + }) + + it('should return did resolution metadata with error if the did contains an unsupported multibase', async () => { + const result = await keyDidResolver.resolve('did:key:asdfkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th') + + expect(result).toEqual({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did 'did:key:asdfkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th': Error: Invalid multibase: asdfkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th`, + }, + }) + }) + + it('should return did resolution metadata with error if the did contains an unsupported multibase', async () => { + const result = await keyDidResolver.resolve('did:key:z6MkmjYasdfasfd8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th') + + expect(result).toEqual({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did 'did:key:z6MkmjYasdfasfd8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th': Error: Unsupported key type from multicodec code '107'`, + }, + }) + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didExample123.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didExample123.json new file mode 100644 index 0000000000..c96d345cf5 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didExample123.json @@ -0,0 +1,94 @@ +{ + "@context": ["https://w3id.org/ns/did/v1"], + "id": "did:example:123", + "alsoKnownAs": ["did:example:456"], + "controller": ["did:example:456"], + "verificationMethod": [ + { + "id": "did:example:123#key-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC X..." + }, + { + "id": "did:example:123#key-2", + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyBase58": "-----BEGIN PUBLIC 9..." + }, + { + "id": "did:example:123#key-3", + "type": "Secp256k1VerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyHex": "-----BEGIN PUBLIC A..." + } + ], + "service": [ + { + "id": "did:example:123#service-1", + "type": "Mediator", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h" + }, + { + "id": "did:example:123#service-2", + "type": "IndyAgent", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h", + "recipientKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "routingKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "priority": 5 + }, + { + "id": "did:example:123#service-3", + "type": "did-communication", + "serviceEndpoint": "https://agent.com/did-comm", + "recipientKeys": ["DADEajsDSaksLng9h"], + "routingKeys": ["DADEajsDSaksLng9h"], + "priority": 10 + } + ], + "authentication": [ + "did:example:123#key-1", + { + "id": "did:example:123#authentication-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "assertionMethod": [ + "did:example:123#key-1", + { + "id": "did:example:123#assertionMethod-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityDelegation": [ + "did:example:123#key-1", + { + "id": "did:example:123#capabilityDelegation-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityInvocation": [ + "did:example:123#key-1", + { + "id": "did:example:123#capabilityInvocation-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "keyAgreement": [ + "did:example:123#key-1", + { + "id": "did:example:123#keyAgreement-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didExample456Invalid.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didExample456Invalid.json new file mode 100644 index 0000000000..8bdf658e8e --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didExample456Invalid.json @@ -0,0 +1,86 @@ +{ + "@context": "https://w3id.org/ns/did/v1", + "id": "did:example:456", + "alsoKnownAs": "did:example:123", + "controller": "did:example:123", + "verificationMethod": [ + "did:example:456#key-1", + { + "id": "did:example:456#key-2", + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyBase58": "-----BEGIN PUBLIC 9..." + }, + { + "id": "did:example:456#key-3", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyHex": "-----BEGIN PUBLIC A..." + } + ], + "service": [ + { + "id": "did:example:123#service-1", + "type": "Mediator" + }, + { + "id": "did:example:123#service-2", + "type": "IndyAgent", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h", + "recipientKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "routingKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "priority": 5 + }, + { + "id": "did:example:123#service-3", + "type": "did-communication", + "serviceEndpoint": "https://agent.com/did-comm", + "recipientKeys": ["DADEajsDSaksLng9h"], + "routingKeys": ["DADEajsDSaksLng9h"], + "priority": 10 + } + ], + "authentication": [ + "did:example:123#key-1", + { + "id": "did:example:123#authentication-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "assertionMethod": [ + "did:example:123#key-1", + { + "id": "did:example:123#assertionMethod-1", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityDelegation": [ + "did:example:123#key-1", + { + "id": "did:example:123#capabilityDelegation-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityInvocation": [ + "did:example:123#key-1", + { + "id": "did:example:123#capabilityInvocation-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "keyAgreement": [ + "did:example:123#key-1", + { + "id": "did:example:123#keyAgreement-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json new file mode 100644 index 0000000000..db96c599ca --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json @@ -0,0 +1,28 @@ +{ + "@context": ["https://w3id.org/ns/did/v1"], + "controller": [], + "alsoKnownAs": [], + "id": "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA", + "verificationMethod": [ + { + "id": "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA#z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA", + "type": "Bls12381G1Key2020", + "controller": "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA", + "publicKeyBase58": "6FywSzB5BPd7xehCo1G4nYHAoZPMMP3gd4PLnvgA6SsTsogtz8K7RDznqLpFPLZXAE" + } + ], + "authentication": [ + "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA#z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA" + ], + "assertionMethod": [ + "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA#z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA" + ], + "capabilityDelegation": [ + "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA#z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA" + ], + "capabilityInvocation": [ + "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA#z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA" + ], + "keyAgreement": [], + "service": [] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json new file mode 100644 index 0000000000..bf15466449 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json @@ -0,0 +1,38 @@ +{ + "@context": ["https://w3id.org/ns/did/v1"], + "controller": [], + "alsoKnownAs": [], + "id": "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s", + "verificationMethod": [ + { + "id": "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#z3tEG5qmJZX29jJSX5kyhDR5YJNnefJFdwTxRqk6zbEPv4Pf2xF12BpmXv9NExxSRFGfxd", + "type": "Bls12381G1Key2020", + "controller": "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s", + "publicKeyBase58": "7BVES4h78wzabPAfMhchXyH5d8EX78S5TtzePH2YkftWcE6by9yj3NTAv9nsyCeYch" + }, + { + "id": "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#zUC7LTa4hWtaE9YKyDsMVGiRNqPMN3s4rjBdB3MFi6PcVWReNfR72y3oGW2NhNcaKNVhMobh7aHp8oZB3qdJCs7RebM2xsodrSm8MmePbN25NTGcpjkJMwKbcWfYDX7eHCJjPGM", + "type": "Bls12381G2Key2020", + "controller": "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s", + "publicKeyBase58": "26d2BdqELsXg7ZHCWKL2D5Y2S7mYrpkdhJemSEEvokd4qy4TULJeeU44hYPGKo4x4DbBp5ARzkv1D6xuB3bmhpdpKAXuXtode67wzh9PCtW8kTqQhH19VSiFZkLNkhe9rtf3" + } + ], + "authentication": [ + "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#z3tEG5qmJZX29jJSX5kyhDR5YJNnefJFdwTxRqk6zbEPv4Pf2xF12BpmXv9NExxSRFGfxd", + "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#zUC7LTa4hWtaE9YKyDsMVGiRNqPMN3s4rjBdB3MFi6PcVWReNfR72y3oGW2NhNcaKNVhMobh7aHp8oZB3qdJCs7RebM2xsodrSm8MmePbN25NTGcpjkJMwKbcWfYDX7eHCJjPGM" + ], + "assertionMethod": [ + "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#z3tEG5qmJZX29jJSX5kyhDR5YJNnefJFdwTxRqk6zbEPv4Pf2xF12BpmXv9NExxSRFGfxd", + "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#zUC7LTa4hWtaE9YKyDsMVGiRNqPMN3s4rjBdB3MFi6PcVWReNfR72y3oGW2NhNcaKNVhMobh7aHp8oZB3qdJCs7RebM2xsodrSm8MmePbN25NTGcpjkJMwKbcWfYDX7eHCJjPGM" + ], + "capabilityDelegation": [ + "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#z3tEG5qmJZX29jJSX5kyhDR5YJNnefJFdwTxRqk6zbEPv4Pf2xF12BpmXv9NExxSRFGfxd", + "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#zUC7LTa4hWtaE9YKyDsMVGiRNqPMN3s4rjBdB3MFi6PcVWReNfR72y3oGW2NhNcaKNVhMobh7aHp8oZB3qdJCs7RebM2xsodrSm8MmePbN25NTGcpjkJMwKbcWfYDX7eHCJjPGM" + ], + "capabilityInvocation": [ + "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#z3tEG5qmJZX29jJSX5kyhDR5YJNnefJFdwTxRqk6zbEPv4Pf2xF12BpmXv9NExxSRFGfxd", + "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#zUC7LTa4hWtaE9YKyDsMVGiRNqPMN3s4rjBdB3MFi6PcVWReNfR72y3oGW2NhNcaKNVhMobh7aHp8oZB3qdJCs7RebM2xsodrSm8MmePbN25NTGcpjkJMwKbcWfYDX7eHCJjPGM" + ], + "keyAgreement": [], + "service": [] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json new file mode 100644 index 0000000000..588fc139fb --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json @@ -0,0 +1,28 @@ +{ + "@context": ["https://w3id.org/ns/did/v1"], + "controller": [], + "alsoKnownAs": [], + "id": "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT", + "verificationMethod": [ + { + "id": "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT#zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT", + "type": "Bls12381G2Key2020", + "controller": "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT", + "publicKeyBase58": "mxE4sHTpbPcmxNviRVR9r7D2taXcNyVJmf9TBUFS1gRt3j3Ej9Seo59GQeCzYwbQgDrfWCwEJvmBwjLvheAky5N2NqFVzk4kuq3S8g4Fmekai4P622vHqWjFrsioYYDqhf9" + } + ], + "authentication": [ + "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT#zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT" + ], + "assertionMethod": [ + "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT#zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT" + ], + "capabilityDelegation": [ + "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT#zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT" + ], + "capabilityInvocation": [ + "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT#zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT" + ], + "keyAgreement": [], + "service": [] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json new file mode 100644 index 0000000000..6c2c14a13f --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://w3id.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1" + ], + "controller": [], + "alsoKnownAs": [], + "id": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", + "verificationMethod": [ + { + "id": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", + "type": "Ed25519VerificationKey2018", + "controller": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", + "publicKeyBase58": "8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K" + }, + { + "id": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6LShpNhGwSupbB7zjuivH156vhLJBDDzmQtA4BY9S94pe1K", + "type": "X25519KeyAgreementKey2019", + "controller": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", + "publicKeyBase58": "79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ" + } + ], + "assertionMethod": [ + "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th" + ], + "authentication": [ + "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th" + ], + "capabilityInvocation": [ + "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th" + ], + "capabilityDelegation": [ + "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th" + ], + "keyAgreement": [ + "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6LShpNhGwSupbB7zjuivH156vhLJBDDzmQtA4BY9S94pe1K" + ], + "service": [] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json new file mode 100644 index 0000000000..b62e8b9d05 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json @@ -0,0 +1,20 @@ +{ + "@context": ["https://w3id.org/ns/did/v1"], + "controller": [], + "alsoKnownAs": [], + "id": "did:key:z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE", + "verificationMethod": [], + "authentication": [], + "assertionMethod": [], + "capabilityDelegation": [], + "capabilityInvocation": [], + "keyAgreement": [ + { + "id": "did:key:z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE#z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE", + "type": "X25519KeyAgreementKey2019", + "controller": "did:key:z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE", + "publicKeyBase58": "6fUMuABnqSDsaGKojbUF3P7ZkEL3wi2njsDdUWZGNgCU" + } + ], + "service": [] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json new file mode 100644 index 0000000000..b138df76ff --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://w3id.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1" + ], + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ", + "alsoKnownAs": [], + "controller": [], + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:R1xKJw17sUoXhejEpugMYJ", + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#key-1", + "publicKeyBase58": "E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu" + }, + { + "type": "X25519KeyAgreementKey2019", + "controller": "did:sov:R1xKJw17sUoXhejEpugMYJ", + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1", + "publicKeyBase58": "Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt" + } + ], + "authentication": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-1"], + "assertionMethod": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-1"], + "keyAgreement": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"], + "capabilityDelegation": [], + "capabilityInvocation": [], + "service": [ + { + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#endpoint", + "type": "endpoint", + "serviceEndpoint": "https://ssi.com" + }, + { + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#profile", + "serviceEndpoint": "https://profile.com", + "type": "profile" + }, + { + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#hub", + "serviceEndpoint": "https://hub.com", + "type": "hub" + } + ] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json new file mode 100644 index 0000000000..5078870923 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json @@ -0,0 +1,53 @@ +{ + "@context": [ + "https://w3id.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1", + "https://didcomm.org/messaging/contexts/v2" + ], + "alsoKnownAs": [], + "controller": [], + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo", + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:WJz9mHyW9BZksioQnRsrAo", + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#key-1", + "publicKeyBase58": "GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8" + }, + { + "type": "X25519KeyAgreementKey2019", + "controller": "did:sov:WJz9mHyW9BZksioQnRsrAo", + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1", + "publicKeyBase58": "S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud" + } + ], + "capabilityDelegation": [], + "capabilityInvocation": [], + "authentication": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-1"], + "assertionMethod": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-1"], + "keyAgreement": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "service": [ + { + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#endpoint", + "type": "endpoint", + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#did-communication", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "routingKeys": ["routingKey1", "routingKey2"], + "accept": ["didcomm/aip2;env=rfc19"], + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#didcomm-1", + "type": "DIDComm", + "serviceEndpoint": "https://agent.com", + "accept": ["didcomm/v2"], + "routingKeys": ["routingKey1", "routingKey2"] + } + ] +} diff --git a/packages/core/src/modules/dids/domain/DidDocument.ts b/packages/core/src/modules/dids/domain/DidDocument.ts new file mode 100644 index 0000000000..d27eb8e324 --- /dev/null +++ b/packages/core/src/modules/dids/domain/DidDocument.ts @@ -0,0 +1,130 @@ +import type { DidDocumentService } from './service' + +import { Expose, Transform, Type } from 'class-transformer' +import { IsArray, IsString, ValidateNested } from 'class-validator' + +import { JsonTransformer } from '../../../utils/JsonTransformer' + +import { IndyAgentService, ServiceTransformer, DidCommService } from './service' +import { VerificationMethodTransformer, VerificationMethod, IsStringOrVerificationMethod } from './verificationMethod' + +interface DidDocumentOptions { + context?: string[] + id: string + alsoKnownAs?: string[] + controller?: string[] + verificationMethod?: VerificationMethod[] + service?: DidDocumentService[] + authentication?: Array + assertionMethod?: Array + keyAgreement?: Array + capabilityInvocation?: Array + capabilityDelegation?: Array +} + +export class DidDocument { + @Expose({ name: '@context' }) + @IsArray() + @Transform((o) => (typeof o.value === 'string' ? [o.value] : o.value), { toClassOnly: true }) + public context = ['https://w3id.org/ns/did/v1'] + + @IsString() + public id!: string + + @IsArray() + @IsString({ each: true }) + public alsoKnownAs: string[] = [] + + @IsArray() + @IsString({ each: true }) + @Transform((o) => (typeof o.value === 'string' ? [o.value] : o.value), { toClassOnly: true }) + public controller: string[] = [] + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => VerificationMethod) + public verificationMethod: VerificationMethod[] = [] + + @IsArray() + @ServiceTransformer() + public service: DidDocumentService[] = [] + + @IsArray() + @VerificationMethodTransformer() + @IsStringOrVerificationMethod({ each: true }) + public authentication: Array = [] + + @IsArray() + @VerificationMethodTransformer() + @IsStringOrVerificationMethod({ each: true }) + public assertionMethod: Array = [] + + @IsArray() + @VerificationMethodTransformer() + @IsStringOrVerificationMethod({ each: true }) + public keyAgreement: Array = [] + + @IsArray() + @VerificationMethodTransformer() + @IsStringOrVerificationMethod({ each: true }) + public capabilityInvocation: Array = [] + + @IsArray() + @VerificationMethodTransformer() + @IsStringOrVerificationMethod({ each: true }) + public capabilityDelegation: Array = [] + + public constructor(options: DidDocumentOptions) { + if (options) { + this.context = options.context ?? this.context + this.id = options.id + this.alsoKnownAs = options.alsoKnownAs ?? this.alsoKnownAs + this.controller = options.controller ?? this.controller + this.verificationMethod = options.verificationMethod ?? this.verificationMethod + this.service = options.service ?? this.service + this.authentication = options.authentication ?? this.authentication + this.assertionMethod = options.assertionMethod ?? this.assertionMethod + this.keyAgreement = options.keyAgreement ?? this.keyAgreement + this.capabilityInvocation = options.capabilityInvocation ?? this.capabilityInvocation + this.capabilityDelegation = options.capabilityDelegation ?? this.capabilityDelegation + } + } + + /** + * Returns all of the service endpoints matching the given type. + * + * @param type The type of service(s) to query. + */ + public getServicesByType(type: string): S[] { + return this.service.filter((service) => service.type === type) as S[] + } + + /** + * Returns all of the service endpoints matching the given class + * + * @param classType The class to query services. + */ + public getServicesByClassType( + classType: new (...args: never[]) => S + ): S[] { + return this.service.filter((service) => service instanceof classType) as S[] + } + + /** + * Get all DIDComm services ordered by priority descending. This means the highest + * priority will be the first entry. + */ + public get didCommServices(): Array { + const didCommServiceTypes = [IndyAgentService.type, DidCommService.type] + const services = this.service.filter((service) => didCommServiceTypes.includes(service.type)) as Array< + IndyAgentService | DidCommService + > + + // Sort services based on indicated priority + return services.sort((a, b) => b.priority - a.priority) + } + + public toJSON() { + return JsonTransformer.toJSON(this) + } +} diff --git a/packages/core/src/modules/dids/domain/DidDocumentBuilder.ts b/packages/core/src/modules/dids/domain/DidDocumentBuilder.ts new file mode 100644 index 0000000000..b7e82daa0a --- /dev/null +++ b/packages/core/src/modules/dids/domain/DidDocumentBuilder.ts @@ -0,0 +1,93 @@ +import type { DidDocumentService } from './service' + +import { DidDocument } from './DidDocument' +import { VerificationMethod } from './verificationMethod' + +export class DidDocumentBuilder { + private didDocument: DidDocument + + public constructor(id: string) { + this.didDocument = new DidDocument({ + id, + }) + } + + public addContext(context: string) { + this.didDocument.context = [...this.didDocument.context, context] + + return this + } + + public addService(service: DidDocumentService) { + this.didDocument.service = [...this.didDocument.service, service] + + return this + } + + public addVerificationMethod(verificationMethod: VerificationMethod) { + this.didDocument.verificationMethod = [ + ...this.didDocument.verificationMethod, + verificationMethod instanceof VerificationMethod + ? verificationMethod + : new VerificationMethod(verificationMethod), + ] + + return this + } + + public addAuthentication(authentication: string | VerificationMethod) { + const verificationMethod = + authentication instanceof VerificationMethod || typeof authentication === 'string' + ? authentication + : new VerificationMethod(authentication) + + this.didDocument.authentication = [...this.didDocument.authentication, verificationMethod] + + return this + } + + public addAssertionMethod(assertionMethod: string | VerificationMethod) { + const verificationMethod = + assertionMethod instanceof VerificationMethod || typeof assertionMethod === 'string' + ? assertionMethod + : new VerificationMethod(assertionMethod) + + this.didDocument.assertionMethod = [...this.didDocument.assertionMethod, verificationMethod] + + return this + } + public addCapabilityDelegation(capabilityDelegation: string | VerificationMethod) { + const verificationMethod = + capabilityDelegation instanceof VerificationMethod || typeof capabilityDelegation === 'string' + ? capabilityDelegation + : new VerificationMethod(capabilityDelegation) + + this.didDocument.capabilityDelegation = [...this.didDocument.capabilityDelegation, verificationMethod] + + return this + } + public addCapabilityInvocation(capabilityInvocation: string | VerificationMethod) { + const verificationMethod = + capabilityInvocation instanceof VerificationMethod || typeof capabilityInvocation === 'string' + ? capabilityInvocation + : new VerificationMethod(capabilityInvocation) + + this.didDocument.capabilityInvocation = [...this.didDocument.capabilityInvocation, verificationMethod] + + return this + } + public addKeyAgreement(keyAgreement: string | VerificationMethod) { + const verificationMethod = + keyAgreement instanceof VerificationMethod || typeof keyAgreement === 'string' + ? keyAgreement + : new VerificationMethod(keyAgreement) + + this.didDocument.keyAgreement = [...this.didDocument.keyAgreement, verificationMethod] + + return this + } + + public build(): DidDocument { + return this.didDocument + } +} diff --git a/packages/core/src/modules/dids/domain/DidKey.ts b/packages/core/src/modules/dids/domain/DidKey.ts new file mode 100644 index 0000000000..5fe63d1101 --- /dev/null +++ b/packages/core/src/modules/dids/domain/DidKey.ts @@ -0,0 +1,220 @@ +import type { DidDocument, VerificationMethod } from '.' + +import { convertPublicKeyToX25519 } from '@stablelib/ed25519' +import { varint } from 'multiformats' + +import { BufferEncoder } from '../../../utils/BufferEncoder' +import { MultiBaseEncoder } from '../../../utils/MultiBaseEncoder' +import { Buffer } from '../../../utils/buffer' +import { parseDid } from '../parse' + +import { DidDocumentBuilder } from './DidDocumentBuilder' + +export const enum KeyType { + ED25519 = 'ed25519', + X25519 = 'x25519', + BLS12381G1 = 'bls12381g1', + BLS12381G2 = 'bls12381g2', + BLS12381G1G2 = 'bls12381g1g2', +} + +const keyTypeResolverMap: Record DidDocument> = { + [KeyType.ED25519]: getEd25519DidDoc, + [KeyType.X25519]: getX25519DidDoc, + [KeyType.BLS12381G1]: getBls12381g1DidDoc, + [KeyType.BLS12381G2]: getBls12381g2DidDoc, + [KeyType.BLS12381G1G2]: getBls12381g1g2DidDoc, +} + +// based on https://github.com/multiformats/multicodec/blob/master/table.csv +const idPrefixMap: Record = { + 234: KeyType.BLS12381G1, + 235: KeyType.BLS12381G2, + 236: KeyType.X25519, + 237: KeyType.ED25519, + 238: KeyType.BLS12381G1G2, +} + +export class DidKey { + public readonly publicKey: Buffer + public readonly keyType: KeyType + + public constructor(publicKey: Uint8Array, keyType: KeyType) { + this.publicKey = Buffer.from(publicKey) + this.keyType = keyType + } + + public static fromDid(did: string) { + const parsed = parseDid(did) + + if (!parsed) { + throw new Error('Unable to parse did') + } + + return DidKey.fromFingerprint(parsed.id) + } + + public static fromPublicKey(publicKey: Uint8Array, keyType: KeyType) { + return new DidKey(Buffer.from(publicKey), keyType) + } + + public static fromPublicKeyBase58(publicKey: string, keyType: KeyType) { + const publicKeyBytes = BufferEncoder.fromBase58(publicKey) + + return DidKey.fromPublicKey(publicKeyBytes, keyType) + } + + public static fromFingerprint(fingerprint: string) { + const { data } = MultiBaseEncoder.decode(fingerprint) + const [code, byteLength] = varint.decode(data) + + const publicKey = Buffer.from(data.slice(byteLength)) + const keyType = idPrefixMap[code] + + if (!keyType) { + throw new Error(`Unsupported key type from multicodec code '${code}'`) + } + + return new DidKey(publicKey, keyType) + } + + public get prefixedPublicKey() { + const codes = Object.keys(idPrefixMap) as unknown as number[] + const code = codes.find((key) => idPrefixMap[key] === this.keyType) as number + + // Create Uint8Array with length of the prefix bytes, then use varint to fill the prefix bytes + const prefixBytes = varint.encodeTo(code, new Uint8Array(varint.encodingLength(code))) + + // Combine prefix with public key + return Buffer.concat([prefixBytes, this.publicKey]) + } + + public get fingerprint() { + return `z${BufferEncoder.toBase58(this.prefixedPublicKey)}` + } + + public get did() { + return `did:key:${this.fingerprint}` + } + + public get didDocument() { + const resolve = keyTypeResolverMap[this.keyType] + + return resolve(this) + } + + public get publicKeyBase58() { + return BufferEncoder.toBase58(this.publicKey) + } + + public get keyId() { + return `${this.did}#${this.fingerprint}` + } +} + +function getBls12381g2DidDoc(didKey: DidKey) { + return getSignatureKeyBase(didKey, { + id: didKey.keyId, + type: 'Bls12381G2Key2020', + controller: didKey.did, + publicKeyBase58: didKey.publicKeyBase58, + }).build() +} + +function getBls12381g1g2DidDoc(didKey: DidKey) { + const g1PublicKey = didKey.publicKey.slice(0, 48) + const g2PublicKey = didKey.publicKey.slice(48) + + const bls12381g1Key = DidKey.fromPublicKey(g1PublicKey, KeyType.BLS12381G1) + const bls12381g2Key = DidKey.fromPublicKey(g2PublicKey, KeyType.BLS12381G2) + + const bls12381g1KeyId = `${didKey.did}#${bls12381g1Key.fingerprint}` + const bls12381g2KeyId = `${didKey.did}#${bls12381g2Key.fingerprint}` + + const didDocumentBuilder = new DidDocumentBuilder(didKey.did) + // BlS12381G1 + .addVerificationMethod({ + id: bls12381g1KeyId, + type: 'Bls12381G1Key2020', + controller: didKey.did, + publicKeyBase58: bls12381g1Key.publicKeyBase58, + }) + .addAuthentication(bls12381g1KeyId) + .addAssertionMethod(bls12381g1KeyId) + .addCapabilityDelegation(bls12381g1KeyId) + .addCapabilityInvocation(bls12381g1KeyId) + // BlS12381G2 + .addVerificationMethod({ + id: bls12381g2KeyId, + type: 'Bls12381G2Key2020', + controller: didKey.did, + publicKeyBase58: bls12381g2Key.publicKeyBase58, + }) + .addAuthentication(bls12381g2KeyId) + .addAssertionMethod(bls12381g2KeyId) + .addCapabilityDelegation(bls12381g2KeyId) + .addCapabilityInvocation(bls12381g2KeyId) + + return didDocumentBuilder.build() +} + +function getBls12381g1DidDoc(didKey: DidKey) { + return getSignatureKeyBase(didKey, { + id: didKey.keyId, + type: 'Bls12381G1Key2020', + controller: didKey.did, + publicKeyBase58: didKey.publicKeyBase58, + }).build() +} + +function getX25519DidDoc(didKey: DidKey) { + const document = new DidDocumentBuilder(didKey.did) + .addKeyAgreement({ + id: didKey.keyId, + type: 'X25519KeyAgreementKey2019', + controller: didKey.did, + publicKeyBase58: didKey.publicKeyBase58, + }) + .build() + + return document +} + +function getEd25519DidDoc(didKey: DidKey) { + const verificationMethod: VerificationMethod = { + id: didKey.keyId, + type: 'Ed25519VerificationKey2018', + controller: didKey.did, + publicKeyBase58: didKey.publicKeyBase58, + } + + const publicKeyX25519 = convertPublicKeyToX25519(didKey.publicKey) + const didKeyX25519 = new DidKey(publicKeyX25519, KeyType.X25519) + const x25519Id = `${didKey.did}#${didKeyX25519.fingerprint}` + + const didDocBuilder = getSignatureKeyBase(didKey, verificationMethod) + + didDocBuilder + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + id: `${didKey.did}#${didKeyX25519.fingerprint}`, + type: 'X25519KeyAgreementKey2019', + controller: didKey.did, + publicKeyBase58: didKeyX25519.publicKeyBase58, + }) + .addKeyAgreement(x25519Id) + + return didDocBuilder.build() +} + +function getSignatureKeyBase(didKey: DidKey, verificationMethod: VerificationMethod) { + const keyId = didKey.keyId + + return new DidDocumentBuilder(didKey.did) + .addVerificationMethod(verificationMethod) + .addAuthentication(keyId) + .addAssertionMethod(keyId) + .addCapabilityDelegation(keyId) + .addCapabilityInvocation(keyId) +} diff --git a/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts b/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts new file mode 100644 index 0000000000..54c53afb5d --- /dev/null +++ b/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts @@ -0,0 +1,275 @@ +import { JsonTransformer } from '../../../../utils/JsonTransformer' +import { MessageValidator } from '../../../../utils/MessageValidator' +import didExample123Fixture from '../../__tests__/__fixtures__/didExample123.json' +import didExample456Invalid from '../../__tests__/__fixtures__/didExample456Invalid.json' +import { DidDocument } from '../DidDocument' +import { DidDocumentService, IndyAgentService, DidCommService } from '../service' +import { VerificationMethod } from '../verificationMethod' + +const didDocumentInstance = new DidDocument({ + id: 'did:example:123', + alsoKnownAs: ['did:example:456'], + controller: ['did:example:456'], + verificationMethod: [ + new VerificationMethod({ + id: 'did:example:123#key-1', + type: 'RsaVerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyPem: '-----BEGIN PUBLIC X...', + }), + new VerificationMethod({ + id: 'did:example:123#key-2', + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyBase58: '-----BEGIN PUBLIC 9...', + }), + new VerificationMethod({ + id: 'did:example:123#key-3', + type: 'Secp256k1VerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyHex: '-----BEGIN PUBLIC A...', + }), + ], + service: [ + new DidDocumentService({ + id: 'did:example:123#service-1', + type: 'Mediator', + serviceEndpoint: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + }), + new IndyAgentService({ + id: 'did:example:123#service-2', + serviceEndpoint: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + recipientKeys: ['Q4zqM7aXqm7gDQkUVLng9h'], + routingKeys: ['Q4zqM7aXqm7gDQkUVLng9h'], + priority: 5, + }), + new DidCommService({ + id: 'did:example:123#service-3', + serviceEndpoint: 'https://agent.com/did-comm', + recipientKeys: ['DADEajsDSaksLng9h'], + routingKeys: ['DADEajsDSaksLng9h'], + priority: 10, + }), + ], + authentication: [ + 'did:example:123#key-1', + new VerificationMethod({ + id: 'did:example:123#authentication-1', + type: 'RsaVerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyPem: '-----BEGIN PUBLIC A...', + }), + ], + assertionMethod: [ + 'did:example:123#key-1', + new VerificationMethod({ + id: 'did:example:123#assertionMethod-1', + type: 'RsaVerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyPem: '-----BEGIN PUBLIC A...', + }), + ], + capabilityDelegation: [ + 'did:example:123#key-1', + new VerificationMethod({ + id: 'did:example:123#capabilityDelegation-1', + type: 'RsaVerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyPem: '-----BEGIN PUBLIC A...', + }), + ], + capabilityInvocation: [ + 'did:example:123#key-1', + new VerificationMethod({ + id: 'did:example:123#capabilityInvocation-1', + type: 'RsaVerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyPem: '-----BEGIN PUBLIC A...', + }), + ], + keyAgreement: [ + 'did:example:123#key-1', + new VerificationMethod({ + id: 'did:example:123#keyAgreement-1', + type: 'RsaVerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyPem: '-----BEGIN PUBLIC A...', + }), + ], +}) + +describe('Did | DidDocument', () => { + it('should correctly transforms Json to DidDoc class', () => { + const didDocument = JsonTransformer.fromJSON(didExample123Fixture, DidDocument) + + // Check other properties + expect(didDocument.id).toBe(didExample123Fixture.id) + expect(didDocument.alsoKnownAs).toEqual(didExample123Fixture.alsoKnownAs) + expect(didDocument.context).toEqual(didExample123Fixture['@context']) + expect(didDocument.controller).toEqual(didExample123Fixture.controller) + + // Check verification method + expect(didDocument.verificationMethod[0]).toBeInstanceOf(VerificationMethod) + expect(didDocument.verificationMethod[1]).toBeInstanceOf(VerificationMethod) + expect(didDocument.verificationMethod[2]).toBeInstanceOf(VerificationMethod) + + // Check Service + expect(didDocument.service[0]).toBeInstanceOf(DidDocumentService) + expect(didDocument.service[1]).toBeInstanceOf(IndyAgentService) + expect(didDocument.service[2]).toBeInstanceOf(DidCommService) + + // Check Authentication + expect(typeof didDocument.authentication[0]).toBe('string') + expect(didDocument.authentication[1]).toBeInstanceOf(VerificationMethod) + + // Check assertionMethod + expect(typeof didDocument.assertionMethod[0]).toBe('string') + expect(didDocument.assertionMethod[1]).toBeInstanceOf(VerificationMethod) + + // Check capabilityDelegation + expect(typeof didDocument.capabilityDelegation[0]).toBe('string') + expect(didDocument.capabilityDelegation[1]).toBeInstanceOf(VerificationMethod) + + // Check capabilityInvocation + expect(typeof didDocument.capabilityInvocation[0]).toBe('string') + expect(didDocument.capabilityInvocation[1]).toBeInstanceOf(VerificationMethod) + + // Check keyAgreement + expect(typeof didDocument.keyAgreement[0]).toBe('string') + expect(didDocument.keyAgreement[1]).toBeInstanceOf(VerificationMethod) + }) + + it('validation should throw an error if the did document is invalid', async () => { + const didDocument = JsonTransformer.fromJSON(didExample456Invalid, DidDocument) + + try { + await MessageValidator.validate(didDocument) + } catch (error) { + expect(error).toMatchObject([ + { + value: 'did:example:123', + property: 'alsoKnownAs', + children: [], + constraints: { isArray: 'alsoKnownAs must be an array' }, + }, + { + value: [ + 'did:example:456#key-1', + { + id: 'did:example:456#key-2', + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyBase58: '-----BEGIN PUBLIC 9...', + }, + { + id: 'did:example:456#key-3', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyHex: '-----BEGIN PUBLIC A...', + }, + ], + property: 'verificationMethod', + children: [ + { + target: [ + 'did:example:456#key-1', + { + id: 'did:example:456#key-2', + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyBase58: '-----BEGIN PUBLIC 9...', + }, + { + id: 'did:example:456#key-3', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyHex: '-----BEGIN PUBLIC A...', + }, + ], + value: 'did:example:456#key-1', + property: '0', + children: [ + { + value: 'did:example:456#key-1', + property: 'verificationMethod', + constraints: { + nestedValidation: 'each value in nested property verificationMethod must be either object or array', + }, + }, + ], + }, + { + target: [ + 'did:example:456#key-1', + { + id: 'did:example:456#key-2', + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyBase58: '-----BEGIN PUBLIC 9...', + }, + { + id: 'did:example:456#key-3', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyHex: '-----BEGIN PUBLIC A...', + }, + ], + value: { + id: 'did:example:456#key-3', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyHex: '-----BEGIN PUBLIC A...', + }, + property: '2', + children: [ + { + target: { + id: 'did:example:456#key-3', + controller: 'did:sov:LjgpST2rjsoxYegQDRm7EL', + publicKeyHex: '-----BEGIN PUBLIC A...', + }, + property: 'type', + children: [], + constraints: { isString: 'type must be a string' }, + }, + ], + }, + ], + }, + ]) + } + }) + + it('should correctly transforms DidDoc class to Json', () => { + const didDocumentJson = JsonTransformer.toJSON(didDocumentInstance) + + expect(didDocumentJson).toMatchObject(didExample123Fixture) + }) + + describe('getServicesByType', () => { + it('returns all services with specified type', async () => { + expect(didDocumentInstance.getServicesByType('IndyAgent')).toEqual( + didDocumentInstance.service.filter((service) => service.type === 'IndyAgent') + ) + }) + }) + + describe('getServicesByClassType', () => { + it('returns all services with specified class', async () => { + expect(didDocumentInstance.getServicesByClassType(IndyAgentService)).toEqual( + didDocumentInstance.service.filter((service) => service instanceof IndyAgentService) + ) + }) + }) + + describe('didCommServices', () => { + it('returns all IndyAgentService and DidCommService instances', async () => { + expect(didDocumentInstance.didCommServices).toEqual( + expect.arrayContaining([didDocumentInstance.service[1], didDocumentInstance.service[2]]) + ) + }) + + it('returns all IndyAgentService and DidCommService instances sorted by priority', async () => { + expect(didDocumentInstance.didCommServices).toEqual([ + didDocumentInstance.service[2], + didDocumentInstance.service[1], + ]) + }) + }) +}) diff --git a/packages/core/src/modules/dids/domain/index.ts b/packages/core/src/modules/dids/domain/index.ts new file mode 100644 index 0000000000..bf0ff1c854 --- /dev/null +++ b/packages/core/src/modules/dids/domain/index.ts @@ -0,0 +1,4 @@ +export * from './service' +export * from './verificationMethod' +export * from './DidDocument' +export * from './DidDocumentBuilder' diff --git a/packages/core/src/modules/dids/domain/service/DidCommService.ts b/packages/core/src/modules/dids/domain/service/DidCommService.ts new file mode 100644 index 0000000000..ef26161a59 --- /dev/null +++ b/packages/core/src/modules/dids/domain/service/DidCommService.ts @@ -0,0 +1,39 @@ +import { ArrayNotEmpty, IsOptional, IsString } from 'class-validator' + +import { DidDocumentService } from './DidDocumentService' + +export class DidCommService extends DidDocumentService { + public constructor(options: { + id: string + serviceEndpoint: string + recipientKeys: string[] + routingKeys?: string[] + accept?: string[] + priority?: number + }) { + super({ ...options, type: DidCommService.type }) + + if (options) { + this.recipientKeys = options.recipientKeys + this.routingKeys = options.routingKeys + this.accept = options.accept + if (options.priority) this.priority = options.priority + } + } + + public static type = 'did-communication' + + @ArrayNotEmpty() + @IsString({ each: true }) + public recipientKeys!: string[] + + @IsString({ each: true }) + @IsOptional() + public routingKeys?: string[] + + @IsString({ each: true }) + @IsOptional() + public accept?: string[] + + public priority = 0 +} diff --git a/packages/core/src/modules/dids/domain/service/DidCommV2Service.ts b/packages/core/src/modules/dids/domain/service/DidCommV2Service.ts new file mode 100644 index 0000000000..c8f04d227a --- /dev/null +++ b/packages/core/src/modules/dids/domain/service/DidCommV2Service.ts @@ -0,0 +1,24 @@ +import { IsOptional, IsString } from 'class-validator' + +import { DidDocumentService } from './DidDocumentService' + +export class DidCommV2Service extends DidDocumentService { + public constructor(options: { id: string; serviceEndpoint: string; routingKeys?: string[]; accept?: string[] }) { + super({ ...options, type: DidCommV2Service.type }) + + if (options) { + this.routingKeys = options.routingKeys + this.accept = options.accept + } + } + + public static type = 'DIDComm' + + @IsString({ each: true }) + @IsOptional() + public routingKeys?: string[] + + @IsString({ each: true }) + @IsOptional() + public accept?: string[] +} diff --git a/packages/core/src/modules/dids/domain/service/DidDocumentService.ts b/packages/core/src/modules/dids/domain/service/DidDocumentService.ts new file mode 100644 index 0000000000..3114076d2f --- /dev/null +++ b/packages/core/src/modules/dids/domain/service/DidDocumentService.ts @@ -0,0 +1,24 @@ +import { IsString } from 'class-validator' + +export class DidDocumentService { + public constructor(options: { id: string; serviceEndpoint: string; type: string }) { + if (options) { + this.id = options.id + this.serviceEndpoint = options.serviceEndpoint + this.type = options.type + } + } + + public get protocolScheme(): string { + return this.serviceEndpoint.split(':')[0] + } + + @IsString() + public id!: string + + @IsString() + public serviceEndpoint!: string + + @IsString() + public type!: string +} diff --git a/packages/core/src/modules/dids/domain/service/IndyAgentService.ts b/packages/core/src/modules/dids/domain/service/IndyAgentService.ts new file mode 100644 index 0000000000..588547fda2 --- /dev/null +++ b/packages/core/src/modules/dids/domain/service/IndyAgentService.ts @@ -0,0 +1,33 @@ +import { ArrayNotEmpty, IsOptional, IsString } from 'class-validator' + +import { DidDocumentService } from './DidDocumentService' + +export class IndyAgentService extends DidDocumentService { + public constructor(options: { + id: string + serviceEndpoint: string + recipientKeys: string[] + routingKeys?: string[] + priority?: number + }) { + super({ ...options, type: IndyAgentService.type }) + + if (options) { + this.recipientKeys = options.recipientKeys + this.routingKeys = options.routingKeys + if (options.priority) this.priority = options.priority + } + } + + public static type = 'IndyAgent' + + @ArrayNotEmpty() + @IsString({ each: true }) + public recipientKeys!: string[] + + @IsString({ each: true }) + @IsOptional() + public routingKeys?: string[] + + public priority = 0 +} diff --git a/packages/core/src/modules/dids/domain/service/ServiceTransformer.ts b/packages/core/src/modules/dids/domain/service/ServiceTransformer.ts new file mode 100644 index 0000000000..6803273476 --- /dev/null +++ b/packages/core/src/modules/dids/domain/service/ServiceTransformer.ts @@ -0,0 +1,40 @@ +import type { ClassConstructor } from 'class-transformer' + +import { Transform, plainToInstance } from 'class-transformer' + +import { DidCommService } from './DidCommService' +import { DidCommV2Service } from './DidCommV2Service' +import { DidDocumentService } from './DidDocumentService' +import { IndyAgentService } from './IndyAgentService' + +export const serviceTypes: { [key: string]: unknown | undefined } = { + [IndyAgentService.type]: IndyAgentService, + [DidCommService.type]: DidCommService, + [DidCommV2Service.type]: DidCommV2Service, +} + +/** + * Decorator that transforms service json to corresponding class instances. See {@link serviceTypes} + * + * @example + * class Example { + * ServiceTransformer() + * private service: Service + * } + */ +export function ServiceTransformer() { + return Transform( + ({ value }: { value: { type: string }[] }) => { + return value.map((serviceJson) => { + const serviceClass = (serviceTypes[serviceJson.type] ?? + DidDocumentService) as ClassConstructor + const service = plainToInstance(serviceClass, serviceJson) + + return service + }) + }, + { + toClassOnly: true, + } + ) +} diff --git a/packages/core/src/modules/dids/domain/service/index.ts b/packages/core/src/modules/dids/domain/service/index.ts new file mode 100644 index 0000000000..83cdf99316 --- /dev/null +++ b/packages/core/src/modules/dids/domain/service/index.ts @@ -0,0 +1,7 @@ +import { DidCommService } from './DidCommService' +import { DidCommV2Service } from './DidCommV2Service' +import { DidDocumentService } from './DidDocumentService' +import { IndyAgentService } from './IndyAgentService' +import { ServiceTransformer, serviceTypes } from './ServiceTransformer' + +export { IndyAgentService, DidCommService, DidDocumentService, DidCommV2Service, ServiceTransformer, serviceTypes } diff --git a/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts new file mode 100644 index 0000000000..a86bd58978 --- /dev/null +++ b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts @@ -0,0 +1,73 @@ +import { IsString, IsOptional } from 'class-validator' + +export interface VerificationMethodOptions { + id: string + type: string + controller: string + publicKeyBase58?: string + publicKeyBase64?: string + publicKeyJwk?: Record + publicKeyHex?: string + publicKeyMultibase?: string + publicKeyPem?: string + blockchainAccountId?: string + ethereumAddress?: string +} + +export class VerificationMethod { + public constructor(options: VerificationMethodOptions) { + if (options) { + this.id = options.id + this.type = options.type + this.controller = options.controller + this.publicKeyBase58 = options.publicKeyBase58 + this.publicKeyBase64 = options.publicKeyBase64 + this.publicKeyJwk = options.publicKeyJwk + this.publicKeyHex = options.publicKeyHex + this.publicKeyMultibase = options.publicKeyMultibase + this.publicKeyPem = options.publicKeyPem + this.blockchainAccountId = options.blockchainAccountId + this.ethereumAddress = options.ethereumAddress + } + } + + @IsString() + public id!: string + + @IsString() + public type!: string + + @IsString() + public controller!: string + + @IsOptional() + @IsString() + public publicKeyBase58?: string + + @IsOptional() + @IsString() + public publicKeyBase64?: string + + // TODO: define JWK structure, we don't support JWK yet + public publicKeyJwk?: Record + + @IsOptional() + @IsString() + public publicKeyHex?: string + + @IsOptional() + @IsString() + public publicKeyMultibase?: string + + @IsOptional() + @IsString() + public publicKeyPem?: string + + @IsOptional() + @IsString() + public blockchainAccountId?: string + + @IsOptional() + @IsString() + public ethereumAddress?: string +} diff --git a/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethodTransformer.ts b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethodTransformer.ts new file mode 100644 index 0000000000..d0ee8ae976 --- /dev/null +++ b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethodTransformer.ts @@ -0,0 +1,59 @@ +import type { ValidationOptions } from 'class-validator' + +import { Transform, TransformationType } from 'class-transformer' +import { isString, ValidateBy, isInstance, buildMessage } from 'class-validator' + +import { JsonTransformer } from '../../../../utils/JsonTransformer' + +import { VerificationMethod } from './VerificationMethod' + +/** + * Checks if a given value is a real string. + */ +function IsStringOrVerificationMethod(validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: 'isStringOrVerificationMethod', + validator: { + validate: (value): boolean => isString(value) || isInstance(value, VerificationMethod), + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + '$property must be a string or instance of VerificationMethod', + validationOptions + ), + }, + }, + validationOptions + ) +} + +/** + * Decorator that transforms authentication json to corresponding class instances + * + * @example + * class Example { + * VerificationMethodTransformer() + * private authentication: VerificationMethod + * } + */ +function VerificationMethodTransformer() { + return Transform(({ value, type }: { value: Array; type: TransformationType }) => { + if (type === TransformationType.PLAIN_TO_CLASS) { + return value.map((auth) => { + // referenced verification method + if (typeof auth === 'string') { + return String(auth) + } + + // embedded verification method + return JsonTransformer.fromJSON(auth, VerificationMethod) + }) + } else if (type === TransformationType.CLASS_TO_PLAIN) { + return value.map((auth) => (typeof auth === 'string' ? auth : JsonTransformer.toJSON(auth))) + } + + // PLAIN_TO_PLAIN + return value + }) +} + +export { IsStringOrVerificationMethod, VerificationMethodTransformer } diff --git a/packages/core/src/modules/dids/domain/verificationMethod/index.ts b/packages/core/src/modules/dids/domain/verificationMethod/index.ts new file mode 100644 index 0000000000..2bfdad4059 --- /dev/null +++ b/packages/core/src/modules/dids/domain/verificationMethod/index.ts @@ -0,0 +1,4 @@ +import { VerificationMethod } from './VerificationMethod' +import { VerificationMethodTransformer, IsStringOrVerificationMethod } from './VerificationMethodTransformer' + +export { VerificationMethod, VerificationMethodTransformer, IsStringOrVerificationMethod } diff --git a/packages/core/src/modules/dids/index.ts b/packages/core/src/modules/dids/index.ts new file mode 100644 index 0000000000..9890f21e98 --- /dev/null +++ b/packages/core/src/modules/dids/index.ts @@ -0,0 +1,3 @@ +export * from './types' +export * from './DidsModule' +export * from './services' diff --git a/packages/core/src/modules/dids/parse.ts b/packages/core/src/modules/dids/parse.ts new file mode 100644 index 0000000000..2fba56ef2e --- /dev/null +++ b/packages/core/src/modules/dids/parse.ts @@ -0,0 +1,7 @@ +import type { ParsedDid } from './types' + +import { parse } from 'did-resolver' + +export function parseDid(did: string): ParsedDid | null { + return parse(did) +} diff --git a/packages/core/src/modules/dids/resolvers/DidResolver.ts b/packages/core/src/modules/dids/resolvers/DidResolver.ts new file mode 100644 index 0000000000..6e0a98537f --- /dev/null +++ b/packages/core/src/modules/dids/resolvers/DidResolver.ts @@ -0,0 +1,6 @@ +import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../types' + +export interface DidResolver { + readonly supportedMethods: string[] + resolve(did: string, parsed: ParsedDid, didResolutionOptions: DidResolutionOptions): Promise +} diff --git a/packages/core/src/modules/dids/resolvers/IndyDidResolver.ts b/packages/core/src/modules/dids/resolvers/IndyDidResolver.ts new file mode 100644 index 0000000000..36d6679093 --- /dev/null +++ b/packages/core/src/modules/dids/resolvers/IndyDidResolver.ts @@ -0,0 +1,134 @@ +import type { IndyEndpointAttrib, IndyLedgerService } from '../../ledger' +import type { ParsedDid, DidResolutionResult } from '../types' +import type { DidResolver } from './DidResolver' + +import { convertPublicKeyToX25519 } from '@stablelib/ed25519' + +import { BufferEncoder } from '../../../utils/BufferEncoder' +import { getFullVerkey } from '../../../utils/did' +import { DidCommService } from '../../connections' +import { DidDocumentService } from '../domain' +import { DidDocumentBuilder } from '../domain/DidDocumentBuilder' +import { DidCommV2Service } from '../domain/service/DidCommV2Service' + +export class IndyDidResolver implements DidResolver { + private indyLedgerService: IndyLedgerService + + public constructor(indyLedgerService: IndyLedgerService) { + this.indyLedgerService = indyLedgerService + } + + public readonly supportedMethods = ['sov'] + + public async resolve(did: string, parsed: ParsedDid): Promise { + const didDocumentMetadata = {} + + try { + const nym = await this.indyLedgerService.getPublicDid(parsed.id) + const endpoints = await this.indyLedgerService.getEndpointsForDid(did) + + const verificationMethodId = `${parsed.did}#key-1` + const keyAgreementId = `${parsed.did}#key-agreement-1` + + const publicKeyBase58 = getFullVerkey(nym.did, nym.verkey) + const publicKeyX25519 = BufferEncoder.toBase58( + convertPublicKeyToX25519(BufferEncoder.fromBase58(publicKeyBase58)) + ) + + const builder = new DidDocumentBuilder(parsed.did) + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: parsed.did, + id: verificationMethodId, + publicKeyBase58: getFullVerkey(nym.did, nym.verkey), + type: 'Ed25519VerificationKey2018', + }) + .addVerificationMethod({ + controller: parsed.did, + id: keyAgreementId, + publicKeyBase58: publicKeyX25519, + type: 'X25519KeyAgreementKey2019', + }) + .addAuthentication(verificationMethodId) + .addAssertionMethod(verificationMethodId) + .addKeyAgreement(keyAgreementId) + + this.addServices(builder, parsed, endpoints, keyAgreementId) + + return { + didDocument: builder.build(), + didDocumentMetadata, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } + + private addServices( + builder: DidDocumentBuilder, + parsed: ParsedDid, + endpoints: IndyEndpointAttrib, + keyAgreementId: string + ) { + const { endpoint, routingKeys, types, ...otherEndpoints } = endpoints + + // If 'endpoint' type add id to the services array + if (endpoint) { + builder.addService( + new DidDocumentService({ + id: `${parsed.did}#endpoint`, + serviceEndpoint: endpoint, + type: 'endpoint', + }) + ) + + // If 'did-communication' included in types, add DIDComm v1 entry + if (types?.includes('did-communication')) { + builder.addService( + new DidCommService({ + id: `${parsed.did}#did-communication`, + serviceEndpoint: endpoint, + priority: 0, + routingKeys: routingKeys ?? [], + recipientKeys: [keyAgreementId], + accept: ['didcomm/aip2;env=rfc19'], + }) + ) + + // If 'DIDComm' included in types, add DIDComm v2 entry + if (types?.includes('DIDComm')) { + builder + .addService( + new DidCommV2Service({ + id: `${parsed.did}#didcomm-1`, + serviceEndpoint: endpoint, + routingKeys: routingKeys ?? [], + accept: ['didcomm/v2'], + }) + ) + .addContext('https://didcomm.org/messaging/contexts/v2') + } + } + } + + // Add other endpoint types + for (const [type, endpoint] of Object.entries(otherEndpoints)) { + builder.addService( + new DidDocumentService({ + id: `${parsed.did}#${type}`, + serviceEndpoint: endpoint as string, + type, + }) + ) + } + } +} diff --git a/packages/core/src/modules/dids/resolvers/KeyDidResolver.ts b/packages/core/src/modules/dids/resolvers/KeyDidResolver.ts new file mode 100644 index 0000000000..0e80cb1f39 --- /dev/null +++ b/packages/core/src/modules/dids/resolvers/KeyDidResolver.ts @@ -0,0 +1,31 @@ +import type { DidResolutionResult } from '../types' +import type { DidResolver } from './DidResolver' + +import { DidKey } from '../domain/DidKey' + +export class KeyDidResolver implements DidResolver { + public readonly supportedMethods = ['key'] + + public async resolve(did: string): Promise { + const didDocumentMetadata = {} + + try { + const didDocument = DidKey.fromDid(did).didDocument + + return { + didDocument: didDocument, + didDocumentMetadata: {}, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } +} diff --git a/packages/core/src/modules/dids/resolvers/WebDidResolver.ts b/packages/core/src/modules/dids/resolvers/WebDidResolver.ts new file mode 100644 index 0000000000..8855855c3b --- /dev/null +++ b/packages/core/src/modules/dids/resolvers/WebDidResolver.ts @@ -0,0 +1,40 @@ +import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../types' +import type { DidResolver } from './DidResolver' + +import { Resolver } from 'did-resolver' +import * as didWeb from 'web-did-resolver' + +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { MessageValidator } from '../../../utils/MessageValidator' +import { DidDocument } from '../domain' + +export class WebDidResolver implements DidResolver { + public readonly supportedMethods + + // FIXME: Would be nice if we don't have to provide a did resolver instance + private _resolverInstance = new Resolver() + private resolver = didWeb.getResolver() + + public constructor() { + this.supportedMethods = Object.keys(this.resolver) + } + + public async resolve( + did: string, + parsed: ParsedDid, + didResolutionOptions: DidResolutionOptions + ): Promise { + const result = await this.resolver[parsed.method](did, parsed, this._resolverInstance, didResolutionOptions) + + let didDocument = null + if (result.didDocument) { + didDocument = JsonTransformer.fromJSON(result.didDocument, DidDocument) + await MessageValidator.validate(didDocument) + } + + return { + ...result, + didDocument, + } + } +} diff --git a/packages/core/src/modules/dids/services/DidResolverService.ts b/packages/core/src/modules/dids/services/DidResolverService.ts new file mode 100644 index 0000000000..812ea39435 --- /dev/null +++ b/packages/core/src/modules/dids/services/DidResolverService.ts @@ -0,0 +1,56 @@ +import type { Logger } from '../../../logger' +import type { DidResolver } from '../resolvers/DidResolver' +import type { DidResolutionOptions, DidResolutionResult, ParsedDid } from '../types' + +import { Lifecycle, scoped } from 'tsyringe' + +import { AgentConfig } from '../../../agent/AgentConfig' +import { IndyLedgerService } from '../../ledger' +import { parseDid } from '../parse' +import { IndyDidResolver } from '../resolvers/IndyDidResolver' +import { KeyDidResolver } from '../resolvers/KeyDidResolver' +import { WebDidResolver } from '../resolvers/WebDidResolver' + +@scoped(Lifecycle.ContainerScoped) +export class DidResolverService { + private logger: Logger + private resolvers: DidResolver[] + + public constructor(agentConfig: AgentConfig, indyLedgerService: IndyLedgerService) { + this.logger = agentConfig.logger + + this.resolvers = [new IndyDidResolver(indyLedgerService), new WebDidResolver(), new KeyDidResolver()] + } + + public async resolve(didUrl: string, options: DidResolutionOptions = {}): Promise { + this.logger.debug(`resolving didUrl ${didUrl}`) + + const result = { + didResolutionMetadata: {}, + didDocument: null, + didDocumentMetadata: {}, + } + + const parsed = parseDid(didUrl) + if (!parsed) { + return { + ...result, + didResolutionMetadata: { error: 'invalidDid' }, + } + } + + const resolver = this.findResolver(parsed) + if (!resolver) { + return { + ...result, + didResolutionMetadata: { error: 'unsupportedDidMethod' }, + } + } + + return resolver.resolve(parsed.did, parsed, options) + } + + private findResolver(parsed: ParsedDid): DidResolver | null { + return this.resolvers.find((r) => r.supportedMethods.includes(parsed.method)) ?? null + } +} diff --git a/packages/core/src/modules/dids/services/index.ts b/packages/core/src/modules/dids/services/index.ts new file mode 100644 index 0000000000..1b4265132d --- /dev/null +++ b/packages/core/src/modules/dids/services/index.ts @@ -0,0 +1 @@ +export * from './DidResolverService' diff --git a/packages/core/src/modules/dids/types.ts b/packages/core/src/modules/dids/types.ts new file mode 100644 index 0000000000..c47e99b00a --- /dev/null +++ b/packages/core/src/modules/dids/types.ts @@ -0,0 +1,13 @@ +import type { DidDocument } from './domain' +import type { DIDResolutionOptions, ParsedDID, DIDDocumentMetadata, DIDResolutionMetadata } from 'did-resolver' + +export type ParsedDid = ParsedDID +export type DidResolutionOptions = DIDResolutionOptions +export type DidDocumentMetadata = DIDDocumentMetadata +export type DidResolutionMetadata = DIDResolutionMetadata + +export interface DidResolutionResult { + didResolutionMetadata: DidResolutionMetadata + didDocument: DidDocument | null + didDocumentMetadata: DidDocumentMetadata +} diff --git a/packages/core/src/modules/ledger/IndyPool.ts b/packages/core/src/modules/ledger/IndyPool.ts index 12cce1a9db..f270a6b628 100644 --- a/packages/core/src/modules/ledger/IndyPool.ts +++ b/packages/core/src/modules/ledger/IndyPool.ts @@ -56,9 +56,6 @@ export class IndyPool { this._poolHandle = undefined - // FIXME: Add type to indy-sdk - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore await this.indy.closePoolLedger(poolHandle) } @@ -68,10 +65,7 @@ export class IndyPool { await this.close() } - // FIXME: Add type to indy-sdk - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - await this.indy.deletePoolLedgerConfig(this.agentConfig.poolName) + await this.indy.deletePoolLedgerConfig(this.poolConfig.id) } public async connect() { diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts index 1e4680081f..e50092ce77 100644 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ b/packages/core/src/modules/ledger/services/IndyLedgerService.ts @@ -90,6 +90,35 @@ export class IndyLedgerService { return didResponse } + public async getEndpointsForDid(did: string) { + const { pool } = await this.indyPoolService.getPoolForDid(did) + + try { + this.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.id}'`) + + const request = await this.indy.buildGetAttribRequest(null, did, 'endpoint', null, null) + + this.logger.debug(`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.id}'`) + const response = await this.submitReadRequest(pool, request) + + if (!response.result.data) return {} + + const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib + this.logger.debug(`Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.id}'`, { + response, + endpoints, + }) + + return endpoints ?? {} + } catch (error) { + this.logger.error(`Error retrieving endpoints for did '${did}' from ledger '${pool.id}'`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + public async registerSchema(did: string, schemaTemplate: SchemaTemplate): Promise { const pool = this.indyPoolService.ledgerWritePool @@ -339,3 +368,10 @@ export interface CredentialDefinitionTemplate { signatureType: 'CL' supportRevocation: boolean } + +export interface IndyEndpointAttrib { + endpoint?: string + types?: Array<'endpoint' | 'did-communication' | 'DIDComm'> + routingKeys?: string[] + [key: string]: unknown +} diff --git a/packages/core/src/storage/IndyStorageService.ts b/packages/core/src/storage/IndyStorageService.ts index c976f18c35..8730f40be4 100644 --- a/packages/core/src/storage/IndyStorageService.ts +++ b/packages/core/src/storage/IndyStorageService.ts @@ -94,7 +94,6 @@ export class IndyStorageService implements StorageService< /** @inheritDoc */ public async save(record: T) { const value = JsonTransformer.serialize(record) - // FIXME: update @types/indy-sdk to be of type Record const tags = this.transformFromRecordTagValues(record.getTags()) as Record try { @@ -112,7 +111,6 @@ export class IndyStorageService implements StorageService< /** @inheritDoc */ public async update(record: T): Promise { const value = JsonTransformer.serialize(record) - // FIXME: update @types/indy-sdk to be of type Record const tags = this.transformFromRecordTagValues(record.getTags()) as Record try { @@ -213,7 +211,6 @@ export class IndyStorageService implements StorageService< // Retrieve records const recordsJson = await this.indy.fetchWalletSearchNextRecords(this.wallet.handle, searchHandle, chunk) - // FIXME: update @types/indy-sdk: records can be null (if last reached) if (recordsJson.records) { records = [...records, ...recordsJson.records] @@ -224,7 +221,7 @@ export class IndyStorageService implements StorageService< // If the number of records returned is less than chunk // It means we reached the end of the iterator (no more records) - if (!records.length || recordsJson.records.length < chunk) { + if (!records.length || !recordsJson.records || recordsJson.records.length < chunk) { await this.indy.closeWalletSearch(searchHandle) return diff --git a/packages/core/src/utils/HashlinkEncoder.ts b/packages/core/src/utils/HashlinkEncoder.ts index 95d947db20..45d28fe821 100644 --- a/packages/core/src/utils/HashlinkEncoder.ts +++ b/packages/core/src/utils/HashlinkEncoder.ts @@ -1,10 +1,10 @@ import type { BaseName } from './MultiBaseEncoder' import type { Buffer } from './buffer' +import { hash as sha256 } from '@stablelib/sha256' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ts is giving me headaches because this package has no types import cbor from 'borc' -import { sha256 } from 'js-sha256' import { BufferEncoder } from './BufferEncoder' import { MultiBaseEncoder } from './MultiBaseEncoder' @@ -89,7 +89,7 @@ export class HashlinkEncoder { baseEncoding: BaseName = 'base58btc' ): string { // TODO: Support more hashing algorithms - const hash = new Uint8Array(sha256.array(buffer)) + const hash = sha256(buffer) const mh = MultiHashEncoder.encode(hash, hashName) const mb = MultiBaseEncoder.encode(mh, baseEncoding) return BufferEncoder.toUtf8String(mb) diff --git a/packages/core/src/utils/did.ts b/packages/core/src/utils/did.ts index bfd9dbb4e2..8622ccbb37 100644 --- a/packages/core/src/utils/did.ts +++ b/packages/core/src/utils/did.ts @@ -48,6 +48,25 @@ export function isSelfCertifiedDid(did: string, verkey: string): boolean { return false } +export function getFullVerkey(did: string, verkey: string) { + if (isFullVerkey(verkey)) return verkey + + // Did could have did:xxx prefix, only take the last item after : + const id = did.split(':').pop() ?? did + // Verkey is prefixed with ~ if abbreviated + const verkeyWithoutTilde = verkey.slice(1) + + // Create base58 encoded public key (32 bytes) + return BufferEncoder.toBase58( + Buffer.concat([ + // Take did identifier (16 bytes) + BufferEncoder.fromBase58(id), + // Concat the abbreviated verkey (16 bytes) + BufferEncoder.fromBase58(verkeyWithoutTilde), + ]) + ) +} + /** * Extract did from credential definition id */ diff --git a/packages/core/tests/dids.test.ts b/packages/core/tests/dids.test.ts new file mode 100644 index 0000000000..34b6178ab9 --- /dev/null +++ b/packages/core/tests/dids.test.ts @@ -0,0 +1,113 @@ +import { Agent } from '../src/agent/Agent' + +import { getBaseConfig } from './helpers' + +import { JsonTransformer } from '@aries-framework/core' + +const { config, agentDependencies } = getBaseConfig('Faber Dids', {}) + +describe('dids', () => { + let agent: Agent + + beforeAll(async () => { + agent = new Agent(config, agentDependencies) + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('should resolve a did:sov did', async () => { + const did = await agent.dids.resolve(`did:sov:TL1EaPFCZ8Si5aUrqScBDt`) + + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/ns/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt', + alsoKnownAs: [], + controller: [], + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt', + id: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-1', + publicKeyBase58: 'FMGcFuU3QwAQLywxvmEnSorQT3NwU9wgDMMTaDFtvswm', + }, + { + controller: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt', + type: 'X25519KeyAgreementKey2019', + id: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-agreement-1', + publicKeyBase58: '6oKfyWDYRpbutQWDUu8ots6GoqAZJ9HYRzPuuEiqfyM', + }, + ], + capabilityDelegation: [], + capabilityInvocation: [], + authentication: ['did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-1'], + assertionMethod: ['did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-1'], + keyAgreement: ['did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-agreement-1'], + service: [], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should resolve a did:key did', async () => { + const did = await agent.dids.resolve('did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') + + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/ns/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + alsoKnownAs: [], + controller: [], + verificationMethod: [ + { + id: 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + publicKeyBase58: '6fioC1zcDPyPEL19pXRS2E4iJ46zH7xP6uSgAaPdwDrx', + }, + { + id: 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6LSrdqo4M24WRDJj1h2hXxgtDTyzjjKCiyapYVgrhwZAySn', + type: 'X25519KeyAgreementKey2019', + controller: 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + publicKeyBase58: 'FxfdY3DCQxVZddKGAtSjZdFW9bCCW7oRwZn1NFJ2Tbg2', + }, + ], + authentication: [ + 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + ], + assertionMethod: [ + 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + ], + capabilityInvocation: [ + 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + ], + capabilityDelegation: [ + 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + ], + keyAgreement: [ + 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6LSrdqo4M24WRDJj1h2hXxgtDTyzjjKCiyapYVgrhwZAySn', + ], + service: [], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 5eda86c01c..019eca5a95 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -505,6 +505,14 @@ export function mockFunction any>(fn: T): jest.Moc return fn as jest.MockedFunction } +/** + * Set a property using a getter value on a mocked oject. + */ +// eslint-disable-next-line @typescript-eslint/ban-types +export function mockProperty(object: T, property: K, value: T[K]) { + Object.defineProperty(object, property, { get: () => value }) +} + export async function setupCredentialTests( faberName: string, aliceName: string, diff --git a/packages/react-native/package.json b/packages/react-native/package.json index d11e930701..f864a85a3f 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -29,7 +29,7 @@ "events": "^3.3.0" }, "devDependencies": { - "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.6", + "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.8", "@types/react-native": "^0.64.10", "indy-sdk-react-native": "^0.1.16", "react": "17.0.1", diff --git a/yarn.lock b/yarn.lock index e4e431b4f8..4fdea81eb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2015,6 +2015,63 @@ resolved "https://registry.yarnpkg.com/@sovpro/delimited-stream/-/delimited-stream-1.1.0.tgz#4334bba7ee241036e580fdd99c019377630d26b4" integrity sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw== +"@stablelib/binary@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/binary/-/binary-1.0.1.tgz#c5900b94368baf00f811da5bdb1610963dfddf7f" + integrity sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q== + dependencies: + "@stablelib/int" "^1.0.1" + +"@stablelib/ed25519@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@stablelib/ed25519/-/ed25519-1.0.2.tgz#937a88a2f73a71d9bdc3ea276efe8954776ae0f4" + integrity sha512-FtnvUwvKbp6l1dNcg4CswMAVFVu/nzLK3oC7/PRtjYyHbWsIkD8j+5cjXHmwcCpdCpRCaTGACkEhhMQ1RcdSOQ== + dependencies: + "@stablelib/random" "^1.0.1" + "@stablelib/sha512" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/hash@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/hash/-/hash-1.0.1.tgz#3c944403ff2239fad8ebb9015e33e98444058bc5" + integrity sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg== + +"@stablelib/int@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/int/-/int-1.0.1.tgz#75928cc25d59d73d75ae361f02128588c15fd008" + integrity sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w== + +"@stablelib/random@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.1.tgz#4357a00cb1249d484a9a71e6054bc7b8324a7009" + integrity sha512-zOh+JHX3XG9MSfIB0LZl/YwPP9w3o6WBiJkZvjPoKKu5LKFW4OLV71vMxWp9qG5T43NaWyn0QQTWgqCdO+yOBQ== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/sha256@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/sha256/-/sha256-1.0.1.tgz#77b6675b67f9b0ea081d2e31bda4866297a3ae4f" + integrity sha512-GIIH3e6KH+91FqGV42Kcj71Uefd/QEe7Dy42sBTeqppXV95ggCcxLTk39bEr+lZfJmp+ghsR07J++ORkRELsBQ== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/sha512@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/sha512/-/sha512-1.0.1.tgz#6da700c901c2c0ceacbd3ae122a38ac57c72145f" + integrity sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/wipe@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/wipe/-/wipe-1.0.1.tgz#d21401f1d59ade56a62e139462a97f104ed19a36" + integrity sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -2144,10 +2201,10 @@ dependencies: "@types/node" "*" -"@types/indy-sdk-react-native@npm:@types/indy-sdk@^1.16.6", "@types/indy-sdk@^1.16.6": - version "1.16.6" - resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.6.tgz#ec8df3dd70cb939c85caa6ebcc32d851e2f3c454" - integrity sha512-BhmqsM2z65aOrg6Hum7YICX02dQA2OS05BjEypdoScmPO3ySsZ5QXngeh6pAi+se5yGYp+cL5msoTqldAhlOGA== +"@types/indy-sdk-react-native@npm:@types/indy-sdk@^1.16.8", "@types/indy-sdk@^1.16.8": + version "1.16.8" + resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.8.tgz#ae3daa067caf91d870ebce219f5cc80c3f9d173f" + integrity sha512-UUfbZ+/6pAYOxRmeWgKaDSg0MJicf+KLFPZv8ckRU+R8AD7oemj9lLjvrOFnv+yYBFsyEw1AfqLg4RfioFZXCA== dependencies: buffer "^6.0.0" @@ -3706,6 +3763,13 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cross-fetch@^3.1.2: + version "3.1.4" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" + integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== + dependencies: + node-fetch "2.6.1" + cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -3937,6 +4001,11 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" +did-resolver@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-3.1.3.tgz#ed530c9daa2c9925f85e9eabf258e51960db7e70" + integrity sha512-ab8y90tSiDkTdfddXRC9Qcb1QSd568aC6+OgFTrcE4rs1vQAZOil+VqXHDu+Ff/UvhxlckPO8oJtp86iICZG0w== + diff-sequences@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" @@ -6176,11 +6245,6 @@ joi@^17.2.1: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" -js-sha256@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" - integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -7250,6 +7314,11 @@ multibase@^4.0.1, multibase@^4.0.4: dependencies: "@multiformats/base-x" "^4.0.1" +multiformats@^9.4.14: + version "9.4.14" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.4.14.tgz#535452892777f5b316685c9f00d5bf6923bebc95" + integrity sha512-X1wtOySaguYL7ua87Gv4+cuvFL3Qi+mpHNcJnzNoyK1NmHy8SfyBIQ1S1KYQoEjXaYoriN+5TP9f4iBJUOQf3A== + multiformats@^9.4.2: version "9.4.3" resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.4.3.tgz#9da626a633ed43a4444b911eaf3344060326be5d" @@ -7334,7 +7403,7 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -10046,6 +10115,14 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-did-resolver@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.8.tgz#9863cb873b10667a72673f571fa48e6bcecc3e11" + integrity sha512-K85NgK3nto5awjBX/5uD9+ZSIMbWIqUoD64G+5NC9EU0OgtV81YcS/++oWVmkOZoH/MVYGLuqajQBx3pQOa29w== + dependencies: + cross-fetch "^3.1.2" + did-resolver "^3.1.3" + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"