From c5c41722e9b626d7cea929faff562c2a69a079fb Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 27 Jan 2022 11:51:18 +0100 Subject: [PATCH] feat: add support for did:peer (#608) Signed-off-by: Timo Glastra --- .eslintrc.js | 6 + packages/core/src/crypto/KeyType.ts | 7 + .../src/crypto/__tests__/JwsService.test.ts | 6 +- packages/core/src/crypto/index.ts | 1 + .../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 | 10 +- .../__tests__/__fixtures__/didExample123.json | 2 +- .../__fixtures__/didExample456Invalid.json | 2 +- .../__fixtures__/didKeyBls12381g1.json | 2 +- .../__fixtures__/didKeyBls12381g1g2.json | 2 +- .../__fixtures__/didKeyBls12381g2.json | 2 +- .../__tests__/__fixtures__/didKeyEd25519.json | 15 +- .../__tests__/__fixtures__/didKeyX25519.json | 2 +- .../__tests__/__fixtures__/didPeer1zQmY.json | 39 ++++ .../didSovR1xKJw17sUoXhejEpugMYJ.json | 2 +- .../didSovWJz9mHyW9BZksioQnRsrAo.json | 2 +- .../modules/dids/__tests__/peer-did.test.ts | 157 +++++++++++++ .../src/modules/dids/domain/DidDocument.ts | 15 +- .../modules/dids/domain/DidDocumentRole.ts | 4 + .../core/src/modules/dids/domain/DidKey.ts | 220 ------------------ .../dids/{resolvers => domain}/DidResolver.ts | 0 packages/core/src/modules/dids/domain/Key.ts | 55 +++++ .../core/src/modules/dids/domain/index.ts | 2 +- .../key-type/__tests__/bls12381g1.test.ts | 82 +++++++ .../key-type/__tests__/bls12381g1g2.test.ts | 111 +++++++++ .../key-type/__tests__/bls12381g2.test.ts | 84 +++++++ .../domain/key-type/__tests__/ed25519.test.ts | 82 +++++++ .../domain/key-type/__tests__/x25519.test.ts | 82 +++++++ .../dids/domain/key-type/bls12381g1.ts | 45 ++++ .../dids/domain/key-type/bls12381g1g2.ts | 48 ++++ .../dids/domain/key-type/bls12381g2.ts | 46 ++++ .../modules/dids/domain/key-type/ed25519.ts | 62 +++++ .../domain/key-type/getSignatureKeyBase.ts | 23 ++ .../src/modules/dids/domain/key-type/index.ts | 2 + .../dids/domain/key-type/keyDidMapping.ts | 73 ++++++ .../dids/domain/key-type/multiCodecKey.ts | 31 +++ .../modules/dids/domain/key-type/x25519.ts | 44 ++++ .../core/src/modules/dids/domain/parse.ts | 13 ++ packages/core/src/modules/dids/index.ts | 2 + .../indy}/IndyDidResolver.ts | 18 +- .../indy}/__tests__/IndyDidResolver.test.ts | 35 +-- .../src/modules/dids/methods/key/DidKey.ts | 28 +++ .../key}/KeyDidResolver.ts | 10 +- .../dids/methods/key/__tests__/DidKey.test.ts | 27 +++ .../key}/__tests__/KeyDidResolver.test.ts | 9 +- .../src/modules/dids/methods/key/index.ts | 1 + .../src/modules/dids/methods/peer/DidPeer.ts | 143 ++++++++++++ .../dids/methods/peer/PeerDidResolver.ts | 49 ++++ .../methods/peer/__tests__/DidPeer.test.ts | 97 ++++++++ .../__tests__/__fixtures__/didPeer1zQmR.json | 32 +++ .../__tests__/__fixtures__/didPeer1zQmZ.json | 27 +++ .../__tests__/__fixtures__/didPeer2ez6L.json | 35 +++ .../peer/__tests__/peerDidNumAlgo2.test.ts | 23 ++ .../dids/methods/peer/peerDidNumAlgo2.ts | 189 +++++++++++++++ .../web}/WebDidResolver.ts | 10 +- packages/core/src/modules/dids/parse.ts | 7 - .../dids/repository/DidDocumentRecord.ts | 52 +++++ .../dids/repository/DidDocumentRepository.ts | 14 ++ .../core/src/modules/dids/repository/index.ts | 2 + .../dids/services/DidResolverService.ts | 31 ++- packages/core/src/utils/index.ts | 3 + packages/core/tests/dids.test.ts | 63 ++++- samples/mediator.ts | 3 +- 67 files changed, 1976 insertions(+), 669 deletions(-) create mode 100644 packages/core/src/crypto/KeyType.ts create mode 100644 packages/core/src/crypto/index.ts delete mode 100644 packages/core/src/modules/dids/__tests__/DidKeyBls12381G1.test.ts delete mode 100644 packages/core/src/modules/dids/__tests__/DidKeyBls12381G1G2.test.ts delete mode 100644 packages/core/src/modules/dids/__tests__/DidKeyBls12381G2.test.ts delete mode 100644 packages/core/src/modules/dids/__tests__/DidKeyEd25519.test.ts delete mode 100644 packages/core/src/modules/dids/__tests__/DidKeyX25519.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/__fixtures__/didPeer1zQmY.json create mode 100644 packages/core/src/modules/dids/__tests__/peer-did.test.ts create mode 100644 packages/core/src/modules/dids/domain/DidDocumentRole.ts delete mode 100644 packages/core/src/modules/dids/domain/DidKey.ts rename packages/core/src/modules/dids/{resolvers => domain}/DidResolver.ts (100%) create mode 100644 packages/core/src/modules/dids/domain/Key.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/bls12381g1.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/bls12381g2.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/ed25519.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/getSignatureKeyBase.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/index.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/multiCodecKey.ts create mode 100644 packages/core/src/modules/dids/domain/key-type/x25519.ts create mode 100644 packages/core/src/modules/dids/domain/parse.ts rename packages/core/src/modules/dids/{resolvers => methods/indy}/IndyDidResolver.ts (88%) rename packages/core/src/modules/dids/{ => methods/indy}/__tests__/IndyDidResolver.test.ts (74%) create mode 100644 packages/core/src/modules/dids/methods/key/DidKey.ts rename packages/core/src/modules/dids/{resolvers => methods/key}/KeyDidResolver.ts (75%) create mode 100644 packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts rename packages/core/src/modules/dids/{ => methods/key}/__tests__/KeyDidResolver.test.ts (89%) create mode 100644 packages/core/src/modules/dids/methods/key/index.ts create mode 100644 packages/core/src/modules/dids/methods/peer/DidPeer.ts create mode 100644 packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts create mode 100644 packages/core/src/modules/dids/methods/peer/__tests__/DidPeer.test.ts create mode 100644 packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmR.json create mode 100644 packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmZ.json create mode 100644 packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer2ez6L.json create mode 100644 packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo2.test.ts create mode 100644 packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts rename packages/core/src/modules/dids/{resolvers => methods/web}/WebDidResolver.ts (79%) delete mode 100644 packages/core/src/modules/dids/parse.ts create mode 100644 packages/core/src/modules/dids/repository/DidDocumentRecord.ts create mode 100644 packages/core/src/modules/dids/repository/DidDocumentRepository.ts create mode 100644 packages/core/src/modules/dids/repository/index.ts diff --git a/.eslintrc.js b/.eslintrc.js index adbe497b4b..c20420e625 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,6 +50,12 @@ module.exports = { devDependencies: false, }, ], + 'no-restricted-imports': [ + 'error', + { + patterns: ['packages/*'], + }, + ], }, overrides: [ { diff --git a/packages/core/src/crypto/KeyType.ts b/packages/core/src/crypto/KeyType.ts new file mode 100644 index 0000000000..858762f670 --- /dev/null +++ b/packages/core/src/crypto/KeyType.ts @@ -0,0 +1,7 @@ +export enum KeyType { + Ed25519 = 'ed25519', + Bls12381g1g2 = 'bls12381g1g2', + Bls12381g1 = 'bls12381g1', + Bls12381g2 = 'bls12381g2', + X25519 = 'x25519', +} diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index 834d9855cc..be1634f95e 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -1,10 +1,11 @@ import type { Wallet } from '@aries-framework/core' import { getAgentConfig } from '../../../tests/helpers' -import { DidKey, KeyType } from '../../modules/dids' +import { DidKey, Key } from '../../modules/dids' import { Buffer, JsonEncoder } from '../../utils' import { IndyWallet } from '../../wallet/IndyWallet' import { JwsService } from '../JwsService' +import { KeyType } from '../KeyType' import * as didJwsz6Mkf from './__fixtures__/didJwsz6Mkf' import * as didJwsz6Mkv from './__fixtures__/didJwsz6Mkv' @@ -31,7 +32,8 @@ describe('JwsService', () => { const { verkey } = await wallet.createDid({ seed: didJwsz6Mkf.SEED }) const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) - const kid = DidKey.fromPublicKeyBase58(verkey, KeyType.ED25519).did + const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519) + const kid = new DidKey(key).did const jws = await jwsService.createJws({ payload, diff --git a/packages/core/src/crypto/index.ts b/packages/core/src/crypto/index.ts new file mode 100644 index 0000000000..208a940d03 --- /dev/null +++ b/packages/core/src/crypto/index.ts @@ -0,0 +1 @@ +export { KeyType } from './KeyType' diff --git a/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1.test.ts b/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1.test.ts deleted file mode 100644 index 27640470ca..0000000000 --- a/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index b094d2aa6a..0000000000 --- a/packages/core/src/modules/dids/__tests__/DidKeyBls12381G1G2.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -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 deleted file mode 100644 index 7daedceea4..0000000000 --- a/packages/core/src/modules/dids/__tests__/DidKeyBls12381G2.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -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 deleted file mode 100644 index c8baf3f9e7..0000000000 --- a/packages/core/src/modules/dids/__tests__/DidKeyEd25519.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index 1a5150fd73..0000000000 --- a/packages/core/src/modules/dids/__tests__/DidKeyX25519.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -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 index 4b38036dd4..ffa2946dac 100644 --- a/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts +++ b/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts @@ -1,21 +1,23 @@ import type { IndyLedgerService } from '../../ledger' +import type { DidDocumentRepository } from '../repository' 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 { parseDid } from '../domain/parse' +import { KeyDidResolver } from '../methods/key/KeyDidResolver' import { DidResolverService } from '../services/DidResolverService' import didKeyEd25519Fixture from './__fixtures__/didKeyEd25519.json' -jest.mock('../resolvers/KeyDidResolver') +jest.mock('../methods/key/KeyDidResolver') const agentConfig = getAgentConfig('DidResolverService') describe('DidResolverService', () => { const indyLedgerServiceMock = jest.fn() as unknown as IndyLedgerService - const didResolverService = new DidResolverService(agentConfig, indyLedgerServiceMock) + const didDocumentRepositoryMock = jest.fn() as unknown as DidDocumentRepository + const didResolverService = new DidResolverService(agentConfig, indyLedgerServiceMock, didDocumentRepositoryMock) it('should correctly find and call the correct resolver for a specified did', async () => { const didKeyResolveSpy = jest.spyOn(KeyDidResolver.prototype, 'resolve') diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didExample123.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didExample123.json index c96d345cf5..32532f721a 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didExample123.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didExample123.json @@ -1,5 +1,5 @@ { - "@context": ["https://w3id.org/ns/did/v1"], + "@context": ["https://w3id.org/did/v1"], "id": "did:example:123", "alsoKnownAs": ["did:example:456"], "controller": ["did:example:456"], diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didExample456Invalid.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didExample456Invalid.json index 8bdf658e8e..17f6c5c251 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didExample456Invalid.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didExample456Invalid.json @@ -1,5 +1,5 @@ { - "@context": "https://w3id.org/ns/did/v1", + "@context": "https://w3id.org/did/v1", "id": "did:example:456", "alsoKnownAs": "did:example:123", "controller": "did:example:123", diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json index db96c599ca..459a3cf420 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json @@ -1,5 +1,5 @@ { - "@context": ["https://w3id.org/ns/did/v1"], + "@context": ["https://w3id.org/did/v1"], "controller": [], "alsoKnownAs": [], "id": "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA", diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json index bf15466449..0b8edff2a0 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json @@ -1,5 +1,5 @@ { - "@context": ["https://w3id.org/ns/did/v1"], + "@context": ["https://w3id.org/did/v1"], "controller": [], "alsoKnownAs": [], "id": "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s", diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json index 588fc139fb..5c3a7dd3f4 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json @@ -1,5 +1,5 @@ { - "@context": ["https://w3id.org/ns/did/v1"], + "@context": ["https://w3id.org/did/v1"], "controller": [], "alsoKnownAs": [], "id": "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT", diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json index 6c2c14a13f..45c8ca8d2a 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json @@ -1,6 +1,6 @@ { "@context": [ - "https://w3id.org/ns/did/v1", + "https://w3id.org/did/v1", "https://w3id.org/security/suites/ed25519-2018/v1", "https://w3id.org/security/suites/x25519-2019/v1" ], @@ -13,12 +13,6 @@ "type": "Ed25519VerificationKey2018", "controller": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", "publicKeyBase58": "8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K" - }, - { - "id": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6LShpNhGwSupbB7zjuivH156vhLJBDDzmQtA4BY9S94pe1K", - "type": "X25519KeyAgreementKey2019", - "controller": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", - "publicKeyBase58": "79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ" } ], "assertionMethod": [ @@ -34,7 +28,12 @@ "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th" ], "keyAgreement": [ - "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6LShpNhGwSupbB7zjuivH156vhLJBDDzmQtA4BY9S94pe1K" + { + "id": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th#z6LShpNhGwSupbB7zjuivH156vhLJBDDzmQtA4BY9S94pe1K", + "type": "X25519KeyAgreementKey2019", + "controller": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", + "publicKeyBase58": "79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ" + } ], "service": [] } diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json index b62e8b9d05..6b7310cd8c 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json @@ -1,5 +1,5 @@ { - "@context": ["https://w3id.org/ns/did/v1"], + "@context": ["https://w3id.org/did/v1"], "controller": [], "alsoKnownAs": [], "id": "did:key:z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE", diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didPeer1zQmY.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didPeer1zQmY.json new file mode 100644 index 0000000000..5a92c2fbf2 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didPeer1zQmY.json @@ -0,0 +1,39 @@ +{ + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmYtsAsQhwEjjFkcJ2zpbHuE1ESuDkTEwm6KQd65HRNtAq", + "alsoKnownAs": [], + "controller": [], + "verificationMethod": [], + "service": [ + { + "id": "#service-0", + "serviceEndpoint": "https://example.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#d0d32199-851f-48e3-b178-6122bd4216a4"], + "routingKeys": [ + "did:key:z6Mkh66d8nyf6EGUaeN2oWFAxv4qxppwUwnmy9crnZoseN7h#z6LSdgnNCDyjAvZHRHfA9rUfrcEk2vndbPsBo85BuZpc1hFC" + ], + "accept": ["didcomm/aip2;env=rfc19"] + } + ], + "authentication": [ + { + "id": "#d0d32199-851f-48e3-b178-6122bd4216a4", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "CQZzRfoJMRzoESU2VtWrgx3rTsk9yjrjqXL2UdxWjX2q" + } + ], + "assertionMethod": [], + "keyAgreement": [ + { + "id": "#08673492-3c44-47fe-baa4-a1780c585d75", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "7SbWSgJgjSvSTc7ZAKHJiaZbTBwNM9TdFUAU1UyZfJn8" + } + ], + "capabilityInvocation": [], + "capabilityDelegation": [] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json index b138df76ff..8bca383077 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json @@ -1,6 +1,6 @@ { "@context": [ - "https://w3id.org/ns/did/v1", + "https://w3id.org/did/v1", "https://w3id.org/security/suites/ed25519-2018/v1", "https://w3id.org/security/suites/x25519-2019/v1" ], diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json index 5078870923..8d975b8304 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json @@ -1,6 +1,6 @@ { "@context": [ - "https://w3id.org/ns/did/v1", + "https://w3id.org/did/v1", "https://w3id.org/security/suites/ed25519-2018/v1", "https://w3id.org/security/suites/x25519-2019/v1", "https://didcomm.org/messaging/contexts/v2" diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts new file mode 100644 index 0000000000..69c72ead3e --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -0,0 +1,157 @@ +import type { IndyLedgerService } from '../../ledger' + +import { getAgentConfig } from '../../../../tests/helpers' +import { KeyType } from '../../../crypto' +import { IndyStorageService } from '../../../storage/IndyStorageService' +import { JsonTransformer } from '../../../utils' +import { IndyWallet } from '../../../wallet/IndyWallet' +import { DidCommService, DidDocument, DidDocumentBuilder, Key } from '../domain' +import { DidDocumentRole } from '../domain/DidDocumentRole' +import { convertPublicKeyToX25519, getEd25519VerificationMethod } from '../domain/key-type/ed25519' +import { getX25519VerificationMethod } from '../domain/key-type/x25519' +import { DidKey } from '../methods/key' +import { DidPeer, PeerDidNumAlgo } from '../methods/peer/DidPeer' +import { DidDocumentRecord, DidDocumentRepository } from '../repository' +import { DidResolverService } from '../services' + +import didPeer1zQmY from './__fixtures__/didPeer1zQmY.json' + +describe('peer dids', () => { + const config = getAgentConfig('Peer DIDs Lifecycle') + + let didDocumentRepository: DidDocumentRepository + let didResolverService: DidResolverService + let wallet: IndyWallet + + beforeEach(async () => { + wallet = new IndyWallet(config) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.initialize(config.walletConfig!) + + const storageService = new IndyStorageService(wallet, config) + didDocumentRepository = new DidDocumentRepository(storageService) + + // Mocking IndyLedgerService as we're only interestd in the did:peer resolver + didResolverService = new DidResolverService(config, {} as unknown as IndyLedgerService, didDocumentRepository) + }) + + afterEach(async () => { + await wallet.delete() + }) + + test('create a peer did method 1 document from ed25519 keys with a service', async () => { + // The following scenario show how we could create a key and create a did document from it for DID Exchange + + const { verkey: publicKeyBase58 } = await wallet.createDid({ seed: 'astringoftotalin32characterslong' }) + const { verkey: mediatorPublicKeyBase58 } = await wallet.createDid({ seed: 'anotherstringof32characterslong1' }) + + const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519) + const x25519Key = Key.fromPublicKey(convertPublicKeyToX25519(ed25519Key.publicKey), KeyType.X25519) + + const ed25519VerificationMethod = getEd25519VerificationMethod({ + // The id can either be the first 8 characters of the key data (for ed25519 it's publicKeyBase58) + // uuid is easier as it is consistent between different key types. Normally you would dynamically + // generate the uuid, but static for testing purposes + id: `#d0d32199-851f-48e3-b178-6122bd4216a4`, + key: ed25519Key, + // For peer dids generated with method 1, the controller MUST be #id as we don't know the did yet + controller: '#id', + }) + const x25519VerificationMethod = getX25519VerificationMethod({ + // The id can either be the first 8 characters of the key data (for ed25519 it's publicKeyBase58) + // uuid is easier as it is consistent between different key types. Normally you would dynamically + // generate the uuid, but static for testing purposes + id: `#08673492-3c44-47fe-baa4-a1780c585d75`, + key: x25519Key, + // For peer dids generated with method 1, the controller MUST be #id as we don't know the did yet + controller: '#id', + }) + + const mediatorEd25519Key = Key.fromPublicKeyBase58(mediatorPublicKeyBase58, KeyType.Ed25519) + const mediatorEd25519DidKey = new DidKey(mediatorEd25519Key) + + const mediatorX25519Key = Key.fromPublicKey(convertPublicKeyToX25519(mediatorEd25519Key.publicKey), KeyType.X25519) + // Use ed25519 did:key, which also includes the x25519 key used for didcomm + const mediatorRoutingKey = `${mediatorEd25519DidKey.did}#${mediatorX25519Key.fingerprint}` + + const service = new DidCommService({ + id: '#service-0', + // Fixme: can we use relative reference (#id) instead of absolute reference here (did:example:123#id)? + // We don't know the did yet + recipientKeys: [ed25519VerificationMethod.id], + serviceEndpoint: 'https://example.com', + accept: ['didcomm/aip2;env=rfc19'], + // It is important that we encode the routing keys as key references. + // So instead of using plain verkeys, we should encode them as did:key dids + routingKeys: [mediatorRoutingKey], + }) + + const didDocument = + // placeholder did, as it is generated from the did document + new DidDocumentBuilder('') + // ed25519 authentication method for signatures + .addAuthentication(ed25519VerificationMethod) + // x25519 for key agreement + .addKeyAgreement(x25519VerificationMethod) + .addService(service) + .build() + + const peerDid = DidPeer.fromDidDocument(didDocument, PeerDidNumAlgo.GenesisDoc) + + expect(peerDid.did).toBe(didPeer1zQmY.id) + expect(peerDid.didDocument).toMatchObject(didPeer1zQmY) + + // Save the record to storage + const didDocumentRecord = new DidDocumentRecord({ + id: didPeer1zQmY.id, + role: DidDocumentRole.Created, + // It is important to take the did document from the PeerDid class + // as it will have the id property + didDocument: peerDid.didDocument, + }) + + await didDocumentRepository.save(didDocumentRecord) + }) + + test('receive a did and did document', async () => { + // This flow assumes peer dids. When implementing for did exchange other did methods could be used + + // We receive the did and did document from the did exchange message (request or response) + const did = didPeer1zQmY.id + + // Note that the did document could be undefined (if inlined did:peer or public did) + const didDocument = JsonTransformer.fromJSON(didPeer1zQmY, DidDocument) + + // Create a did peer instance from the did document document, or only the did if no did document provided + const didPeer = didDocument ? DidPeer.fromDidDocument(didDocument) : DidPeer.fromDid(did) + + // make sure the dids are valid by matching them against our encoded variants + expect(didPeer.did).toBe(did) + + // If a did document was provided, we match it against the did document of the peer did + // This validates whether we get the same did document + if (didDocument) { + expect(didPeer.didDocument.toJSON()).toMatchObject(didPeer1zQmY) + } + + // If the method is a genesis doc (did:peer:1) we should store the document + // Otherwise this is not needed as we can generate it form the did itself + if (didPeer.numAlgo === PeerDidNumAlgo.GenesisDoc) { + const didDocumentRecord = new DidDocumentRecord({ + id: didPeer.did, + role: DidDocumentRole.Received, + didDocument: didPeer.didDocument, + }) + + await didDocumentRepository.save(didDocumentRecord) + } + + // Then we save the did (not the did document) in the connection record + // connectionRecord.theirDid = didPeer.did + + // Then when we want to send a message we can resolve the did document + const { didDocument: resolvedDidDocument } = await didResolverService.resolve(didPeer.did) + expect(resolvedDidDocument).toBeInstanceOf(DidDocument) + expect(resolvedDidDocument?.toJSON()).toMatchObject(didPeer1zQmY) + }) +}) diff --git a/packages/core/src/modules/dids/domain/DidDocument.ts b/packages/core/src/modules/dids/domain/DidDocument.ts index d27eb8e324..dda46608a3 100644 --- a/packages/core/src/modules/dids/domain/DidDocument.ts +++ b/packages/core/src/modules/dids/domain/DidDocument.ts @@ -26,7 +26,7 @@ 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'] + public context = ['https://w3id.org/did/v1'] @IsString() public id!: string @@ -90,6 +90,19 @@ export class DidDocument { } } + public dereferenceKey(keyId: string) { + // TODO: once we use JSON-LD we should use that to resolve references in did documents. + // for now we check whether the key id ends with the keyId. + // so if looking for #123 and key.id is did:key:123#123, it is valid. But #123 as key.id is also valid + const verificationMethod = this.verificationMethod.find((key) => key.id.endsWith(keyId)) + + if (!verificationMethod) { + throw new Error(`Unable to locate verification with id '${keyId}'`) + } + + return verificationMethod + } + /** * Returns all of the service endpoints matching the given type. * diff --git a/packages/core/src/modules/dids/domain/DidDocumentRole.ts b/packages/core/src/modules/dids/domain/DidDocumentRole.ts new file mode 100644 index 0000000000..66ba66e488 --- /dev/null +++ b/packages/core/src/modules/dids/domain/DidDocumentRole.ts @@ -0,0 +1,4 @@ +export enum DidDocumentRole { + Created = 'created', + Received = 'received', +} diff --git a/packages/core/src/modules/dids/domain/DidKey.ts b/packages/core/src/modules/dids/domain/DidKey.ts deleted file mode 100644 index 5fe63d1101..0000000000 --- a/packages/core/src/modules/dids/domain/DidKey.ts +++ /dev/null @@ -1,220 +0,0 @@ -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/resolvers/DidResolver.ts b/packages/core/src/modules/dids/domain/DidResolver.ts similarity index 100% rename from packages/core/src/modules/dids/resolvers/DidResolver.ts rename to packages/core/src/modules/dids/domain/DidResolver.ts diff --git a/packages/core/src/modules/dids/domain/Key.ts b/packages/core/src/modules/dids/domain/Key.ts new file mode 100644 index 0000000000..413dd1dff6 --- /dev/null +++ b/packages/core/src/modules/dids/domain/Key.ts @@ -0,0 +1,55 @@ +import type { KeyType } from '../../../crypto' + +import { varint } from 'multiformats' + +import { Buffer, BufferEncoder, MultiBaseEncoder } from '../../../utils' + +import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeytype } from './key-type/multiCodecKey' + +export class Key { + public readonly publicKey: Buffer + public readonly keyType: KeyType + + public constructor(publicKey: Uint8Array, keyType: KeyType) { + this.publicKey = Buffer.from(publicKey) + this.keyType = keyType + } + + public static fromPublicKey(publicKey: Uint8Array, keyType: KeyType) { + return new Key(Buffer.from(publicKey), keyType) + } + + public static fromPublicKeyBase58(publicKey: string, keyType: KeyType) { + const publicKeyBytes = BufferEncoder.fromBase58(publicKey) + + return Key.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 = getKeyTypeByMultiCodecPrefix(code) + + return new Key(publicKey, keyType) + } + + public get prefixedPublicKey() { + const multiCodecPrefix = getMultiCodecPrefixByKeytype(this.keyType) + + // Create Uint8Array with length of the prefix bytes, then use varint to fill the prefix bytes + const prefixBytes = varint.encodeTo(multiCodecPrefix, new Uint8Array(varint.encodingLength(multiCodecPrefix))) + + // Combine prefix with public key + return Buffer.concat([prefixBytes, this.publicKey]) + } + + public get fingerprint() { + return `z${BufferEncoder.toBase58(this.prefixedPublicKey)}` + } + + public get publicKeyBase58() { + return BufferEncoder.toBase58(this.publicKey) + } +} diff --git a/packages/core/src/modules/dids/domain/index.ts b/packages/core/src/modules/dids/domain/index.ts index e6cca204e5..5e2bbcd60f 100644 --- a/packages/core/src/modules/dids/domain/index.ts +++ b/packages/core/src/modules/dids/domain/index.ts @@ -1,5 +1,5 @@ export * from './service' export * from './verificationMethod' export * from './DidDocument' -export * from './DidKey' export * from './DidDocumentBuilder' +export * from './Key' diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts new file mode 100644 index 0000000000..3a043c3410 --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts @@ -0,0 +1,82 @@ +import { KeyType } from '../../../../../crypto' +import { JsonTransformer, BufferEncoder, Buffer } from '../../../../../utils' +import keyBls12381g1Fixture from '../../../__tests__/__fixtures__/didKeyBls12381g1.json' +import { Key } from '../../Key' +import { VerificationMethod } from '../../verificationMethod' +import { keyDidBls12381g1 } from '../bls12381g1' + +const TEST_BLS12381G1_BASE58_KEY = '6FywSzB5BPd7xehCo1G4nYHAoZPMMP3gd4PLnvgA6SsTsogtz8K7RDznqLpFPLZXAE' +const TEST_BLS12381G1_FINGERPRINT = 'z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA' +const TEST_BLS12381G1_DID = `did:key:${TEST_BLS12381G1_FINGERPRINT}` +const TEST_BLS12381G1_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([234, 1]), + BufferEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY), +]) + +describe('bls12381g1', () => { + it('creates a Key instance from public key bytes and bls12381g1 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY) + + const key = Key.fromPublicKey(publicKeyBytes, KeyType.Bls12381g1) + + expect(key.fingerprint).toBe(TEST_BLS12381G1_FINGERPRINT) + }) + + it('creates a Key instance from a base58 encoded public key and bls12381g1 key type', async () => { + const key = Key.fromPublicKeyBase58(TEST_BLS12381G1_BASE58_KEY, KeyType.Bls12381g1) + + expect(key.fingerprint).toBe(TEST_BLS12381G1_FINGERPRINT) + }) + + it('creates a Key instance from a fingerprint', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G1_FINGERPRINT) + + expect(key.publicKeyBase58).toBe(TEST_BLS12381G1_BASE58_KEY) + }) + + it('should correctly calculate the getter properties', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G1_FINGERPRINT) + + expect(key.fingerprint).toBe(TEST_BLS12381G1_FINGERPRINT) + expect(key.publicKeyBase58).toBe(TEST_BLS12381G1_BASE58_KEY) + expect(key.publicKey).toEqual(BufferEncoder.fromBase58(TEST_BLS12381G1_BASE58_KEY)) + expect(key.keyType).toBe(KeyType.Bls12381g1) + expect(key.prefixedPublicKey.equals(TEST_BLS12381G1_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G1_FINGERPRINT) + const didDocument = keyDidBls12381g1.getDidDocument(TEST_BLS12381G1_DID, key) + + expect(JsonTransformer.toJSON(didDocument)).toMatchObject(keyBls12381g1Fixture) + }) + + it('should return a valid verification method', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G1_FINGERPRINT) + const verificationMethods = keyDidBls12381g1.getVerificationMethods(TEST_BLS12381G1_DID, key) + + expect(JsonTransformer.toJSON(verificationMethods)).toMatchObject([keyBls12381g1Fixture.verificationMethod[0]]) + }) + + it('supports Bls12381G1Key2020 verification method type', () => { + expect(keyDidBls12381g1.supportedVerificationMethodTypes).toMatchObject(['Bls12381G1Key2020']) + }) + + it('returns key for Bls12381G1Key2020 verification method', () => { + const verificationMethod = JsonTransformer.fromJSON(keyBls12381g1Fixture.verificationMethod[0], VerificationMethod) + + const key = keyDidBls12381g1.getKeyFromVerificationMethod(verificationMethod) + + expect(key.fingerprint).toBe(TEST_BLS12381G1_FINGERPRINT) + }) + + it('throws an error if an invalid verification method is passed', () => { + const verificationMethod = JsonTransformer.fromJSON(keyBls12381g1Fixture.verificationMethod[0], VerificationMethod) + + verificationMethod.type = 'SomeRandomType' + + expect(() => keyDidBls12381g1.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + 'Invalid verification method passed' + ) + }) +}) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts new file mode 100644 index 0000000000..3609decc6e --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts @@ -0,0 +1,111 @@ +import { KeyType } from '../../../../../crypto' +import { JsonTransformer, BufferEncoder, Buffer } from '../../../../../utils' +import keyBls12381g1g2Fixture from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' +import { Key } from '../../Key' +import { VerificationMethod } from '../../verificationMethod' +import { keyDidBls12381g1g2 } from '../bls12381g1g2' + +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_BLS12381G2_BASE58_KEY = + '26d2BdqELsXg7ZHCWKL2D5Y2S7mYrpkdhJemSEEvokd4qy4TULJeeU44hYPGKo4x4DbBp5ARzkv1D6xuB3bmhpdpKAXuXtode67wzh9PCtW8kTqQhH19VSiFZkLNkhe9rtf3' +const TEST_BLS12381G2_FINGERPRINT = + 'zUC7LTa4hWtaE9YKyDsMVGiRNqPMN3s4rjBdB3MFi6PcVWReNfR72y3oGW2NhNcaKNVhMobh7aHp8oZB3qdJCs7RebM2xsodrSm8MmePbN25NTGcpjkJMwKbcWfYDX7eHCJjPGM' + +const TEST_BLS12381G1G2_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([238, 1]), + BufferEncoder.fromBase58(TEST_BLS12381G1G2_BASE58_KEY), +]) + +describe('bls12381g1g2', () => { + it('creates a Key instance from public key bytes and bls12381g1g2 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_BLS12381G1G2_BASE58_KEY) + + const key = Key.fromPublicKey(publicKeyBytes, KeyType.Bls12381g1g2) + + expect(key.fingerprint).toBe(TEST_BLS12381G1G2_FINGERPRINT) + }) + + it('creates a Key instance from a base58 encoded public key and bls12381g1g2 key type', async () => { + const key = Key.fromPublicKeyBase58(TEST_BLS12381G1G2_BASE58_KEY, KeyType.Bls12381g1g2) + + expect(key.fingerprint).toBe(TEST_BLS12381G1G2_FINGERPRINT) + }) + + it('creates a Key instance from a fingerprint', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G1G2_FINGERPRINT) + + expect(key.publicKeyBase58).toBe(TEST_BLS12381G1G2_BASE58_KEY) + }) + + it('should correctly calculate the getter properties', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G1G2_FINGERPRINT) + + expect(key.fingerprint).toBe(TEST_BLS12381G1G2_FINGERPRINT) + expect(key.publicKeyBase58).toBe(TEST_BLS12381G1G2_BASE58_KEY) + expect(key.publicKey).toEqual(BufferEncoder.fromBase58(TEST_BLS12381G1G2_BASE58_KEY)) + expect(key.keyType).toBe(KeyType.Bls12381g1g2) + expect(key.prefixedPublicKey.equals(TEST_BLS12381G1G2_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G1G2_FINGERPRINT) + const didDocument = keyDidBls12381g1g2.getDidDocument(TEST_BLS12381G1G2_DID, key) + + expect(JsonTransformer.toJSON(didDocument)).toMatchObject(keyBls12381g1g2Fixture) + }) + + it('should return a valid verification method', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G1G2_FINGERPRINT) + const verificationMethods = keyDidBls12381g1g2.getVerificationMethods(TEST_BLS12381G1G2_DID, key) + + expect(JsonTransformer.toJSON(verificationMethods)).toMatchObject(keyBls12381g1g2Fixture.verificationMethod) + }) + + it('supports no verification method type', () => { + // Verification methods can be handled by g1 or g2 key types. No reason to do it in here + expect(keyDidBls12381g1g2.supportedVerificationMethodTypes).toMatchObject([]) + }) + + it('throws an error for getKeyFromVerificationMethod as it is not supported for bls12381g1g2 key types', () => { + const verificationMethod = JsonTransformer.fromJSON( + keyBls12381g1g2Fixture.verificationMethod[0], + VerificationMethod + ) + + expect(() => keyDidBls12381g1g2.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + 'Not supported for bls12381g1g2 key' + ) + }) + + it('should correctly go from g1g2 to g1', async () => { + const g1g2Key = Key.fromFingerprint(TEST_BLS12381G1G2_FINGERPRINT) + + const g1PublicKey = g1g2Key.publicKey.slice(0, 48) + const g1DidKey = Key.fromPublicKey(g1PublicKey, KeyType.Bls12381g1) + + expect(g1DidKey.fingerprint).toBe(TEST_BLS12381G1_FINGERPRINT) + 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 g1g2Key = Key.fromFingerprint(TEST_BLS12381G1G2_FINGERPRINT) + + const g2PublicKey = g1g2Key.publicKey.slice(48) + const g2DidKey = Key.fromPublicKey(g2PublicKey, KeyType.Bls12381g2) + + expect(g2DidKey.fingerprint).toBe(TEST_BLS12381G2_FINGERPRINT) + 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/domain/key-type/__tests__/bls12381g2.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts new file mode 100644 index 0000000000..34f68c8850 --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts @@ -0,0 +1,84 @@ +import { KeyType } from '../../../../../crypto' +import { JsonTransformer, BufferEncoder, Buffer } from '../../../../../utils' +import keyBls12381g2Fixture from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' +import { Key } from '../../Key' +import { VerificationMethod } from '../../verificationMethod' +import { keyDidBls12381g2 } from '../bls12381g2' + +const TEST_BLS12381G2_BASE58_KEY = + 'mxE4sHTpbPcmxNviRVR9r7D2taXcNyVJmf9TBUFS1gRt3j3Ej9Seo59GQeCzYwbQgDrfWCwEJvmBwjLvheAky5N2NqFVzk4kuq3S8g4Fmekai4P622vHqWjFrsioYYDqhf9' +const TEST_BLS12381G2_FINGERPRINT = + 'zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT' +const TEST_BLS12381G2_DID = `did:key:${TEST_BLS12381G2_FINGERPRINT}` +const TEST_BLS12381G2_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([235, 1]), + BufferEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY), +]) + +describe('bls12381g2', () => { + it('creates a Key instance from public key bytes and bls12381g2 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY) + + const key = Key.fromPublicKey(publicKeyBytes, KeyType.Bls12381g2) + + expect(key.fingerprint).toBe(TEST_BLS12381G2_FINGERPRINT) + }) + + it('creates a Key instance from a base58 encoded public key and bls12381g2 key type', async () => { + const key = Key.fromPublicKeyBase58(TEST_BLS12381G2_BASE58_KEY, KeyType.Bls12381g2) + + expect(key.fingerprint).toBe(TEST_BLS12381G2_FINGERPRINT) + }) + + it('creates a Key instance from a fingerprint', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G2_FINGERPRINT) + + expect(key.publicKeyBase58).toBe(TEST_BLS12381G2_BASE58_KEY) + }) + + it('should correctly calculate the getter properties', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G2_FINGERPRINT) + + expect(key.fingerprint).toBe(TEST_BLS12381G2_FINGERPRINT) + expect(key.publicKeyBase58).toBe(TEST_BLS12381G2_BASE58_KEY) + expect(key.publicKey).toEqual(BufferEncoder.fromBase58(TEST_BLS12381G2_BASE58_KEY)) + expect(key.keyType).toBe(KeyType.Bls12381g2) + expect(key.prefixedPublicKey.equals(TEST_BLS12381G2_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G2_FINGERPRINT) + const didDocument = keyDidBls12381g2.getDidDocument(TEST_BLS12381G2_DID, key) + + expect(JsonTransformer.toJSON(didDocument)).toMatchObject(keyBls12381g2Fixture) + }) + + it('should return a valid verification method', async () => { + const key = Key.fromFingerprint(TEST_BLS12381G2_FINGERPRINT) + const verificationMethods = keyDidBls12381g2.getVerificationMethods(TEST_BLS12381G2_DID, key) + + expect(JsonTransformer.toJSON(verificationMethods)).toMatchObject([keyBls12381g2Fixture.verificationMethod[0]]) + }) + + it('supports Bls12381G2Key2020 verification method type', () => { + expect(keyDidBls12381g2.supportedVerificationMethodTypes).toMatchObject(['Bls12381G2Key2020']) + }) + + it('returns key for Bls12381G2Key2020 verification method', () => { + const verificationMethod = JsonTransformer.fromJSON(keyBls12381g2Fixture.verificationMethod[0], VerificationMethod) + + const key = keyDidBls12381g2.getKeyFromVerificationMethod(verificationMethod) + + expect(key.fingerprint).toBe(TEST_BLS12381G2_FINGERPRINT) + }) + + it('throws an error if an invalid verification method is passed', () => { + const verificationMethod = JsonTransformer.fromJSON(keyBls12381g2Fixture.verificationMethod[0], VerificationMethod) + + verificationMethod.type = 'SomeRandomType' + + expect(() => keyDidBls12381g2.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + 'Invalid verification method passed' + ) + }) +}) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts new file mode 100644 index 0000000000..2b37d842f9 --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts @@ -0,0 +1,82 @@ +import { KeyType } from '../../../../../crypto' +import { JsonTransformer, BufferEncoder, Buffer } from '../../../../../utils' +import didKeyEd25519Fixture from '../../../__tests__/__fixtures__//didKeyEd25519.json' +import { Key } from '../../../domain/Key' +import { VerificationMethod } from '../../../domain/verificationMethod' +import { keyDidEd25519 } from '../ed25519' + +const TEST_ED25519_BASE58_KEY = '8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K' +const TEST_ED25519_FINGERPRINT = 'z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th' +const TEST_ED25519_DID = `did:key:${TEST_ED25519_FINGERPRINT}` +const TEST_ED25519_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([237, 1]), + BufferEncoder.fromBase58(TEST_ED25519_BASE58_KEY), +]) + +describe('ed25519', () => { + it('creates a Key instance from public key bytes and ed25519 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_ED25519_BASE58_KEY) + + const didKey = Key.fromPublicKey(publicKeyBytes, KeyType.Ed25519) + + expect(didKey.fingerprint).toBe(TEST_ED25519_FINGERPRINT) + }) + + it('creates a Key instance from a base58 encoded public key and ed25519 key type', async () => { + const didKey = Key.fromPublicKeyBase58(TEST_ED25519_BASE58_KEY, KeyType.Ed25519) + + expect(didKey.fingerprint).toBe(TEST_ED25519_FINGERPRINT) + }) + + it('creates a Key instance from a fingerprint', async () => { + const didKey = Key.fromFingerprint(TEST_ED25519_FINGERPRINT) + + expect(didKey.fingerprint).toBe(TEST_ED25519_FINGERPRINT) + }) + + it('should correctly calculate the getter properties', async () => { + const didKey = Key.fromFingerprint(TEST_ED25519_FINGERPRINT) + + expect(didKey.fingerprint).toBe(TEST_ED25519_FINGERPRINT) + 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.prefixedPublicKey.equals(TEST_ED25519_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const key = Key.fromFingerprint(TEST_ED25519_FINGERPRINT) + const didDocument = keyDidEd25519.getDidDocument(TEST_ED25519_DID, key) + + expect(JsonTransformer.toJSON(didDocument)).toMatchObject(didKeyEd25519Fixture) + }) + + it('should return a valid verification method', async () => { + const key = Key.fromFingerprint(TEST_ED25519_FINGERPRINT) + const verificationMethods = keyDidEd25519.getVerificationMethods(TEST_ED25519_DID, key) + + expect(JsonTransformer.toJSON(verificationMethods)).toMatchObject([didKeyEd25519Fixture.verificationMethod[0]]) + }) + + it('supports Ed25519VerificationKey2018 verification method type', () => { + expect(keyDidEd25519.supportedVerificationMethodTypes).toMatchObject(['Ed25519VerificationKey2018']) + }) + + it('returns key for Ed25519VerificationKey2018 verification method', () => { + const verificationMethod = JsonTransformer.fromJSON(didKeyEd25519Fixture.verificationMethod[0], VerificationMethod) + + const key = keyDidEd25519.getKeyFromVerificationMethod(verificationMethod) + + expect(key.fingerprint).toBe(TEST_ED25519_FINGERPRINT) + }) + + it('throws an error if an invalid verification method is passed', () => { + const verificationMethod = JsonTransformer.fromJSON(didKeyEd25519Fixture.verificationMethod[0], VerificationMethod) + + verificationMethod.type = 'SomeRandomType' + + expect(() => keyDidEd25519.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + 'Invalid verification method passed' + ) + }) +}) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts new file mode 100644 index 0000000000..82692acc2b --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts @@ -0,0 +1,82 @@ +import { KeyType } from '../../../../../crypto' +import { JsonTransformer, BufferEncoder, Buffer } from '../../../../../utils' +import didKeyX25519Fixture from '../../../__tests__/__fixtures__/didKeyX25519.json' +import { Key } from '../../Key' +import { VerificationMethod } from '../../verificationMethod' +import { keyDidX25519 } from '../x25519' + +const TEST_X25519_BASE58_KEY = '6fUMuABnqSDsaGKojbUF3P7ZkEL3wi2njsDdUWZGNgCU' +const TEST_X25519_FINGERPRINT = 'z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE' +const TEST_X25519_DID = `did:key:${TEST_X25519_FINGERPRINT}` +const TEST_X25519_PREFIX_BYTES = Buffer.concat([ + new Uint8Array([236, 1]), + BufferEncoder.fromBase58(TEST_X25519_BASE58_KEY), +]) + +describe('x25519', () => { + it('creates a Key instance from public key bytes and x25519 key type', async () => { + const publicKeyBytes = BufferEncoder.fromBase58(TEST_X25519_BASE58_KEY) + + const didKey = Key.fromPublicKey(publicKeyBytes, KeyType.X25519) + + expect(didKey.fingerprint).toBe(TEST_X25519_FINGERPRINT) + }) + + it('creates a Key instance from a base58 encoded public key and x25519 key type', async () => { + const didKey = Key.fromPublicKeyBase58(TEST_X25519_BASE58_KEY, KeyType.X25519) + + expect(didKey.fingerprint).toBe(TEST_X25519_FINGERPRINT) + }) + + it('creates a Key instance from a fingerprint', async () => { + const didKey = Key.fromFingerprint(TEST_X25519_FINGERPRINT) + + expect(didKey.fingerprint).toBe(TEST_X25519_FINGERPRINT) + }) + + it('should correctly calculate the getter properties', async () => { + const didKey = Key.fromFingerprint(TEST_X25519_FINGERPRINT) + + expect(didKey.fingerprint).toBe(TEST_X25519_FINGERPRINT) + 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.prefixedPublicKey.equals(TEST_X25519_PREFIX_BYTES)).toBe(true) + }) + + it('should return a valid did:key did document for the did', async () => { + const key = Key.fromFingerprint(TEST_X25519_FINGERPRINT) + const didDocument = keyDidX25519.getDidDocument(TEST_X25519_DID, key) + + expect(JsonTransformer.toJSON(didDocument)).toMatchObject(didKeyX25519Fixture) + }) + + it('should return a valid verification method', async () => { + const key = Key.fromFingerprint(TEST_X25519_FINGERPRINT) + const verificationMethods = keyDidX25519.getVerificationMethods(TEST_X25519_DID, key) + + expect(JsonTransformer.toJSON(verificationMethods)).toMatchObject([didKeyX25519Fixture.keyAgreement[0]]) + }) + + it('supports X25519KeyAgreementKey2019 verification method type', () => { + expect(keyDidX25519.supportedVerificationMethodTypes).toMatchObject(['X25519KeyAgreementKey2019']) + }) + + it('returns key for X25519KeyAgreementKey2019 verification method', () => { + const verificationMethod = JsonTransformer.fromJSON(didKeyX25519Fixture.keyAgreement[0], VerificationMethod) + + const key = keyDidX25519.getKeyFromVerificationMethod(verificationMethod) + + expect(key.fingerprint).toBe(TEST_X25519_FINGERPRINT) + }) + + it('throws an error if an invalid verification method is passed', () => { + const verificationMethod = JsonTransformer.fromJSON(didKeyX25519Fixture.keyAgreement[0], VerificationMethod) + + verificationMethod.type = 'SomeRandomType' + + expect(() => keyDidX25519.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + 'Invalid verification method passed' + ) + }) +}) diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts new file mode 100644 index 0000000000..18fe54bc0a --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts @@ -0,0 +1,45 @@ +import type { VerificationMethod } from '../verificationMethod' +import type { KeyDidMapping } from './keyDidMapping' + +import { KeyType } from '../../../../crypto' +import { Key } from '../Key' + +import { getSignatureKeyBase } from './getSignatureKeyBase' + +const VERIFICATION_METHOD_TYPE_BLS12381G1_KEY_2020 = 'Bls12381G1Key2020' + +export function getBls12381g1VerificationMethod(did: string, key: Key) { + return { + id: `${did}#${key.fingerprint}`, + type: VERIFICATION_METHOD_TYPE_BLS12381G1_KEY_2020, + controller: did, + publicKeyBase58: key.publicKeyBase58, + } +} + +export function getBls12381g1DidDoc(did: string, key: Key) { + const verificationMethod = getBls12381g1VerificationMethod(did, key) + + return getSignatureKeyBase({ + did, + key, + verificationMethod, + }).build() +} + +export const keyDidBls12381g1: KeyDidMapping = { + supportedVerificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G1_KEY_2020], + + getDidDocument: getBls12381g1DidDoc, + getVerificationMethods: (did, key) => [getBls12381g1VerificationMethod(did, key)], + getKeyFromVerificationMethod: (verificationMethod: VerificationMethod) => { + if ( + verificationMethod.type !== VERIFICATION_METHOD_TYPE_BLS12381G1_KEY_2020 || + !verificationMethod.publicKeyBase58 + ) { + throw new Error('Invalid verification method passed') + } + + return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Bls12381g1) + }, +} diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts new file mode 100644 index 0000000000..88f84783fc --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts @@ -0,0 +1,48 @@ +import type { KeyDidMapping } from './keyDidMapping' + +import { KeyType } from '../../../../crypto' +import { DidDocumentBuilder } from '../DidDocumentBuilder' +import { Key } from '../Key' + +import { getBls12381g1VerificationMethod } from './bls12381g1' +import { getBls12381g2VerificationMethod } from './bls12381g2' + +export function getBls12381g1g2VerificationMethod(did: string, key: Key) { + const g1PublicKey = key.publicKey.slice(0, 48) + const g2PublicKey = key.publicKey.slice(48) + + const bls12381g1Key = Key.fromPublicKey(g1PublicKey, KeyType.Bls12381g1) + const bls12381g2Key = Key.fromPublicKey(g2PublicKey, KeyType.Bls12381g2) + + const bls12381g1VerificationMethod = getBls12381g1VerificationMethod(did, bls12381g1Key) + const bls12381g2VerificationMethod = getBls12381g2VerificationMethod(did, bls12381g2Key) + + return [bls12381g1VerificationMethod, bls12381g2VerificationMethod] +} + +export function getBls12381g1g2DidDoc(did: string, key: Key) { + const verificationMethods = getBls12381g1g2VerificationMethod(did, key) + + const didDocumentBuilder = new DidDocumentBuilder(did) + + for (const verificationMethod of verificationMethods) { + didDocumentBuilder + .addVerificationMethod(verificationMethod) + .addAuthentication(verificationMethod.id) + .addAssertionMethod(verificationMethod.id) + .addCapabilityDelegation(verificationMethod.id) + .addCapabilityInvocation(verificationMethod.id) + } + + return didDocumentBuilder.build() +} + +export const keyDidBls12381g1g2: KeyDidMapping = { + supportedVerificationMethodTypes: [], + getDidDocument: getBls12381g1g2DidDoc, + getVerificationMethods: getBls12381g1g2VerificationMethod, + + getKeyFromVerificationMethod: () => { + throw new Error('Not supported for bls12381g1g2 key') + }, +} diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts new file mode 100644 index 0000000000..1d215b61fd --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts @@ -0,0 +1,46 @@ +import type { VerificationMethod } from '../verificationMethod' +import type { KeyDidMapping } from './keyDidMapping' + +import { KeyType } from '../../../../crypto' +import { Key } from '../Key' + +import { getSignatureKeyBase } from './getSignatureKeyBase' + +const VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 = 'Bls12381G2Key2020' + +export function getBls12381g2VerificationMethod(did: string, key: Key) { + return { + id: `${did}#${key.fingerprint}`, + type: VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020, + controller: did, + publicKeyBase58: key.publicKeyBase58, + } +} + +export function getBls12381g2DidDoc(did: string, key: Key) { + const verificationMethod = getBls12381g2VerificationMethod(did, key) + + return getSignatureKeyBase({ + did, + key, + verificationMethod, + }).build() +} + +export const keyDidBls12381g2: KeyDidMapping = { + supportedVerificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + + getDidDocument: getBls12381g2DidDoc, + getVerificationMethods: (did, key) => [getBls12381g2VerificationMethod(did, key)], + + getKeyFromVerificationMethod: (verificationMethod: VerificationMethod) => { + if ( + verificationMethod.type !== VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 || + !verificationMethod.publicKeyBase58 + ) { + throw new Error('Invalid verification method passed') + } + + return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Bls12381g2) + }, +} diff --git a/packages/core/src/modules/dids/domain/key-type/ed25519.ts b/packages/core/src/modules/dids/domain/key-type/ed25519.ts new file mode 100644 index 0000000000..11fd98bf7c --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/ed25519.ts @@ -0,0 +1,62 @@ +import type { VerificationMethod } from '../verificationMethod' +import type { KeyDidMapping } from './keyDidMapping' + +import { convertPublicKeyToX25519 } from '@stablelib/ed25519' + +import { KeyType } from '../../../../crypto' +import { Key } from '../Key' + +import { getSignatureKeyBase } from './getSignatureKeyBase' +import { getX25519VerificationMethod } from './x25519' + +const VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018 = 'Ed25519VerificationKey2018' + +export function getEd25519VerificationMethod({ key, id, controller }: { id: string; key: Key; controller: string }) { + return { + id, + type: VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, + controller, + publicKeyBase58: key.publicKeyBase58, + } +} + +export function getEd25519DidDoc(did: string, key: Key) { + const verificationMethod = getEd25519VerificationMethod({ id: `${did}#${key.fingerprint}`, key, controller: did }) + + const publicKeyX25519 = convertPublicKeyToX25519(key.publicKey) + const didKeyX25519 = Key.fromPublicKey(publicKeyX25519, KeyType.X25519) + const x25519VerificationMethod = getX25519VerificationMethod({ + id: `${did}#${didKeyX25519.fingerprint}`, + key: didKeyX25519, + controller: did, + }) + + const didDocBuilder = getSignatureKeyBase({ did, key, verificationMethod }) + + didDocBuilder + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addKeyAgreement(x25519VerificationMethod) + + return didDocBuilder.build() +} + +export const keyDidEd25519: KeyDidMapping = { + supportedVerificationMethodTypes: [VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018], + getDidDocument: getEd25519DidDoc, + getVerificationMethods: (did, key) => [ + getEd25519VerificationMethod({ id: `${did}#${key.fingerprint}`, key, controller: did }), + ], + getKeyFromVerificationMethod: (verificationMethod: VerificationMethod) => { + if ( + verificationMethod.type !== VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018 || + !verificationMethod.publicKeyBase58 + ) { + throw new Error('Invalid verification method passed') + } + + return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Ed25519) + }, +} + +export { convertPublicKeyToX25519 } diff --git a/packages/core/src/modules/dids/domain/key-type/getSignatureKeyBase.ts b/packages/core/src/modules/dids/domain/key-type/getSignatureKeyBase.ts new file mode 100644 index 0000000000..377c8111bd --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/getSignatureKeyBase.ts @@ -0,0 +1,23 @@ +import type { Key } from '../Key' +import type { VerificationMethod } from '../verificationMethod' + +import { DidDocumentBuilder } from '../DidDocumentBuilder' + +export function getSignatureKeyBase({ + did, + key, + verificationMethod, +}: { + did: string + key: Key + verificationMethod: VerificationMethod +}) { + const keyId = `${did}#${key.fingerprint}` + + return new DidDocumentBuilder(did) + .addVerificationMethod(verificationMethod) + .addAuthentication(keyId) + .addAssertionMethod(keyId) + .addCapabilityDelegation(keyId) + .addCapabilityInvocation(keyId) +} diff --git a/packages/core/src/modules/dids/domain/key-type/index.ts b/packages/core/src/modules/dids/domain/key-type/index.ts new file mode 100644 index 0000000000..ce5cbb0a5d --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/index.ts @@ -0,0 +1,2 @@ +export { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeytype } from './multiCodecKey' +export { getKeyDidMappingByKeyType, getKeyDidMappingByVerificationMethod } from './keyDidMapping' diff --git a/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts new file mode 100644 index 0000000000..760d8b40db --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts @@ -0,0 +1,73 @@ +import type { DidDocument } from '../DidDocument' +import type { Key } from '../Key' +import type { VerificationMethod } from '../verificationMethod' + +import { KeyType } from '../../../../crypto' + +import { keyDidBls12381g1 } from './bls12381g1' +import { keyDidBls12381g1g2 } from './bls12381g1g2' +import { keyDidBls12381g2 } from './bls12381g2' +import { keyDidEd25519 } from './ed25519' +import { keyDidX25519 } from './x25519' + +export interface KeyDidMapping { + getVerificationMethods: (did: string, key: Key) => VerificationMethod[] + getDidDocument: (did: string, key: Key) => DidDocument + getKeyFromVerificationMethod(verificationMethod: VerificationMethod): Key + supportedVerificationMethodTypes: string[] +} + +// TODO: Maybe we should make this dynamically? +const keyDidMapping: Record = { + [KeyType.Ed25519]: keyDidEd25519, + [KeyType.X25519]: keyDidX25519, + [KeyType.Bls12381g1]: keyDidBls12381g1, + [KeyType.Bls12381g2]: keyDidBls12381g2, + [KeyType.Bls12381g1g2]: keyDidBls12381g1g2, +} + +/** + * Dynamically creates a mapping from verification method key type to the key Did interface + * for all key types. + * + * { + * "Ed25519VerificationKey2018": KeyDidMapping + * } + */ +const verificationMethodKeyDidMapping = Object.values(KeyType).reduce>( + (mapping, keyType) => { + const supported = keyDidMapping[keyType].supportedVerificationMethodTypes.reduce>( + (accumulator, vMethodKeyType) => ({ + ...accumulator, + [vMethodKeyType]: keyDidMapping[keyType], + }), + {} + ) + + return { + ...mapping, + ...supported, + } + }, + {} +) + +export function getKeyDidMappingByKeyType(keyType: KeyType) { + const keyDid = keyDidMapping[keyType] + + if (!keyDid) { + throw new Error(`Unsupported key did from key type '${keyType}'`) + } + + return keyDid +} + +export function getKeyDidMappingByVerificationMethod(verificationMethod: VerificationMethod) { + const keyDid = verificationMethodKeyDidMapping[verificationMethod.type] + + if (!keyDid) { + throw new Error(`Unsupported key did from verification method type '${verificationMethod.type}'`) + } + + return keyDid +} diff --git a/packages/core/src/modules/dids/domain/key-type/multiCodecKey.ts b/packages/core/src/modules/dids/domain/key-type/multiCodecKey.ts new file mode 100644 index 0000000000..884145f1da --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/multiCodecKey.ts @@ -0,0 +1,31 @@ +import { KeyType } from '../../../../crypto' + +// based on https://github.com/multiformats/multicodec/blob/master/table.csv +const multiCodecPrefixMap: Record = { + 234: KeyType.Bls12381g1, + 235: KeyType.Bls12381g2, + 236: KeyType.X25519, + 237: KeyType.Ed25519, + 238: KeyType.Bls12381g1g2, +} + +export function getKeyTypeByMultiCodecPrefix(multiCodecPrefix: number): KeyType { + const keyType = multiCodecPrefixMap[multiCodecPrefix] + + if (!keyType) { + throw new Error(`Unsupported key type from multicodec code '${multiCodecPrefix}'`) + } + + return keyType +} + +export function getMultiCodecPrefixByKeytype(keyType: KeyType): number { + const codes = Object.keys(multiCodecPrefixMap) + const code = codes.find((key) => multiCodecPrefixMap[key] === keyType) + + if (!code) { + throw new Error(`Could not find multicodec prefix for key type '${keyType}'`) + } + + return Number(code) +} diff --git a/packages/core/src/modules/dids/domain/key-type/x25519.ts b/packages/core/src/modules/dids/domain/key-type/x25519.ts new file mode 100644 index 0000000000..943e2027ae --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/x25519.ts @@ -0,0 +1,44 @@ +import type { VerificationMethod } from '../verificationMethod' +import type { KeyDidMapping } from './keyDidMapping' + +import { KeyType } from '../../../../crypto' +import { DidDocumentBuilder } from '../DidDocumentBuilder' +import { Key } from '../Key' + +const VERIFICATION_METHOD_TYPE_X25519_KEY_AGREEMENT_KEY_2019 = 'X25519KeyAgreementKey2019' + +export function getX25519VerificationMethod({ key, id, controller }: { id: string; key: Key; controller: string }) { + return { + id, + type: VERIFICATION_METHOD_TYPE_X25519_KEY_AGREEMENT_KEY_2019, + controller, + publicKeyBase58: key.publicKeyBase58, + } +} + +export function getX25519DidDoc(did: string, key: Key) { + const verificationMethod = getX25519VerificationMethod({ id: `${did}#${key.fingerprint}`, key, controller: did }) + + const document = new DidDocumentBuilder(did).addKeyAgreement(verificationMethod).build() + + return document +} + +export const keyDidX25519: KeyDidMapping = { + supportedVerificationMethodTypes: [VERIFICATION_METHOD_TYPE_X25519_KEY_AGREEMENT_KEY_2019], + + getDidDocument: getX25519DidDoc, + getVerificationMethods: (did, key) => [ + getX25519VerificationMethod({ id: `${did}#${key.fingerprint}`, key, controller: did }), + ], + getKeyFromVerificationMethod: (verificationMethod: VerificationMethod) => { + if ( + verificationMethod.type !== VERIFICATION_METHOD_TYPE_X25519_KEY_AGREEMENT_KEY_2019 || + !verificationMethod.publicKeyBase58 + ) { + throw new Error('Invalid verification method passed') + } + + return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.X25519) + }, +} diff --git a/packages/core/src/modules/dids/domain/parse.ts b/packages/core/src/modules/dids/domain/parse.ts new file mode 100644 index 0000000000..aebeccec6f --- /dev/null +++ b/packages/core/src/modules/dids/domain/parse.ts @@ -0,0 +1,13 @@ +import type { ParsedDid } from '../types' + +import { parse } from 'did-resolver' + +export function parseDid(did: string): ParsedDid { + const parsed = parse(did) + + if (!parsed) { + throw new Error(`Error parsing did '${did}'`) + } + + return parsed +} diff --git a/packages/core/src/modules/dids/index.ts b/packages/core/src/modules/dids/index.ts index 77ddf8c78c..aec5563aca 100644 --- a/packages/core/src/modules/dids/index.ts +++ b/packages/core/src/modules/dids/index.ts @@ -2,3 +2,5 @@ export * from './types' export * from './domain' export * from './DidsModule' export * from './services' +export { DidKey } from './methods/key/DidKey' +export { DidPeer } from './methods/peer/DidPeer' diff --git a/packages/core/src/modules/dids/resolvers/IndyDidResolver.ts b/packages/core/src/modules/dids/methods/indy/IndyDidResolver.ts similarity index 88% rename from packages/core/src/modules/dids/resolvers/IndyDidResolver.ts rename to packages/core/src/modules/dids/methods/indy/IndyDidResolver.ts index 36d6679093..cbfcc04032 100644 --- a/packages/core/src/modules/dids/resolvers/IndyDidResolver.ts +++ b/packages/core/src/modules/dids/methods/indy/IndyDidResolver.ts @@ -1,15 +1,15 @@ -import type { IndyEndpointAttrib, IndyLedgerService } from '../../ledger' -import type { ParsedDid, DidResolutionResult } from '../types' -import type { DidResolver } from './DidResolver' +import type { IndyEndpointAttrib, IndyLedgerService } from '../../../ledger' +import type { DidResolver } from '../../domain/DidResolver' +import type { ParsedDid, DidResolutionResult } from '../../types' 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' +import { BufferEncoder } from '../../../../utils/BufferEncoder' +import { getFullVerkey } from '../../../../utils/did' +import { DidDocumentService } from '../../domain' +import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' +import { DidCommService } from '../../domain/service/DidCommService' +import { DidCommV2Service } from '../../domain/service/DidCommV2Service' export class IndyDidResolver implements DidResolver { private indyLedgerService: IndyLedgerService diff --git a/packages/core/src/modules/dids/__tests__/IndyDidResolver.test.ts b/packages/core/src/modules/dids/methods/indy/__tests__/IndyDidResolver.test.ts similarity index 74% rename from packages/core/src/modules/dids/__tests__/IndyDidResolver.test.ts rename to packages/core/src/modules/dids/methods/indy/__tests__/IndyDidResolver.test.ts index 9280b73eaf..3a46e189c9 100644 --- a/packages/core/src/modules/dids/__tests__/IndyDidResolver.test.ts +++ b/packages/core/src/modules/dids/methods/indy/__tests__/IndyDidResolver.test.ts @@ -1,28 +1,17 @@ -import type { IndyEndpointAttrib } from '../../ledger/services/IndyLedgerService' +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 { mockFunction } from '../../../../../../tests/helpers' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { IndyLedgerService } from '../../../../ledger/services/IndyLedgerService' +import didSovR1xKJw17sUoXhejEpugMYJFixture from '../../../__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' +import didSovWJz9mHyW9BZksioQnRsrAoFixture from '../../../__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json' +import { parseDid } from '../../../domain/parse' +import { IndyDidResolver } from '../IndyDidResolver' -import didSovR1xKJw17sUoXhejEpugMYJFixture from './__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' -import didSovWJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json' - -jest.mock('../../ledger/services/IndyLedgerService') +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 @@ -51,7 +40,7 @@ describe('DidResolver', () => { mockFunction(ledgerService.getPublicDid).mockResolvedValue(nymResponse) mockFunction(ledgerService.getEndpointsForDid).mockResolvedValue(endpoints) - const result = await indyDidResolver.resolve(did, getParsed(did)) + const result = await indyDidResolver.resolve(did, parseDid(did)) expect(JsonTransformer.toJSON(result)).toMatchObject({ didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture, @@ -80,7 +69,7 @@ describe('DidResolver', () => { mockFunction(ledgerService.getPublicDid).mockReturnValue(Promise.resolve(nymResponse)) mockFunction(ledgerService.getEndpointsForDid).mockReturnValue(Promise.resolve(endpoints)) - const result = await indyDidResolver.resolve(did, getParsed(did)) + const result = await indyDidResolver.resolve(did, parseDid(did)) expect(JsonTransformer.toJSON(result)).toMatchObject({ didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture, @@ -96,7 +85,7 @@ describe('DidResolver', () => { mockFunction(ledgerService.getPublicDid).mockRejectedValue(new Error('Error retrieving did')) - const result = await indyDidResolver.resolve(did, getParsed(did)) + const result = await indyDidResolver.resolve(did, parseDid(did)) expect(result).toMatchObject({ didDocument: null, diff --git a/packages/core/src/modules/dids/methods/key/DidKey.ts b/packages/core/src/modules/dids/methods/key/DidKey.ts new file mode 100644 index 0000000000..7f6fbb3c51 --- /dev/null +++ b/packages/core/src/modules/dids/methods/key/DidKey.ts @@ -0,0 +1,28 @@ +import { Key } from '../../domain/Key' +import { getKeyDidMappingByKeyType } from '../../domain/key-type' +import { parseDid } from '../../domain/parse' + +export class DidKey { + public readonly key: Key + + public constructor(key: Key) { + this.key = key + } + + public static fromDid(did: string) { + const parsed = parseDid(did) + + const key = Key.fromFingerprint(parsed.id) + return new DidKey(key) + } + + public get did() { + return `did:key:${this.key.fingerprint}` + } + + public get didDocument() { + const { getDidDocument } = getKeyDidMappingByKeyType(this.key.keyType) + + return getDidDocument(this.did, this.key) + } +} diff --git a/packages/core/src/modules/dids/resolvers/KeyDidResolver.ts b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts similarity index 75% rename from packages/core/src/modules/dids/resolvers/KeyDidResolver.ts rename to packages/core/src/modules/dids/methods/key/KeyDidResolver.ts index 0e80cb1f39..eb7d4ee5ae 100644 --- a/packages/core/src/modules/dids/resolvers/KeyDidResolver.ts +++ b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts @@ -1,7 +1,7 @@ -import type { DidResolutionResult } from '../types' -import type { DidResolver } from './DidResolver' +import type { DidResolver } from '../../domain/DidResolver' +import type { DidResolutionResult } from '../../types' -import { DidKey } from '../domain/DidKey' +import { DidKey } from './DidKey' export class KeyDidResolver implements DidResolver { public readonly supportedMethods = ['key'] @@ -13,8 +13,8 @@ export class KeyDidResolver implements DidResolver { const didDocument = DidKey.fromDid(did).didDocument return { - didDocument: didDocument, - didDocumentMetadata: {}, + didDocument, + didDocumentMetadata, didResolutionMetadata: { contentType: 'application/did+ld+json' }, } } catch (error) { diff --git a/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts new file mode 100644 index 0000000000..661feea4e2 --- /dev/null +++ b/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts @@ -0,0 +1,27 @@ +import { KeyType } from '../../../../../crypto' +import didKeyBls12381g1 from '../../../__tests__/__fixtures__/didKeyBls12381g1.json' +import didKeyBls12381g1g2 from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' +import didKeyBls12381g2 from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' +import didKeyEd25519 from '../../../__tests__/__fixtures__/didKeyEd25519.json' +import didKeyX25519 from '../../../__tests__/__fixtures__/didKeyX25519.json' +import { Key } from '../../../domain/Key' +import { DidKey } from '../DidKey' + +describe('DidKey', () => { + it('creates a DidKey instance from a did', async () => { + const documentTypes = [didKeyX25519, didKeyEd25519, didKeyBls12381g1, didKeyBls12381g2, didKeyBls12381g1g2] + + for (const documentType of documentTypes) { + const didKey = DidKey.fromDid(documentType.id) + expect(didKey.didDocument.toJSON()).toMatchObject(documentType) + } + }) + + it('creates a DidKey instance from a key instance', async () => { + const key = Key.fromPublicKeyBase58(didKeyX25519.keyAgreement[0].publicKeyBase58, KeyType.X25519) + const didKey = new DidKey(key) + + expect(didKey.did).toBe(didKeyX25519.id) + expect(didKey.didDocument.toJSON()).toMatchObject(didKeyX25519) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/KeyDidResolver.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidResolver.test.ts similarity index 89% rename from packages/core/src/modules/dids/__tests__/KeyDidResolver.test.ts rename to packages/core/src/modules/dids/methods/key/__tests__/KeyDidResolver.test.ts index a9a24f90a2..b9b7509c30 100644 --- a/packages/core/src/modules/dids/__tests__/KeyDidResolver.test.ts +++ b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidResolver.test.ts @@ -1,8 +1,7 @@ -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { DidKey } from '../domain/DidKey' -import { KeyDidResolver } from '../resolvers/KeyDidResolver' - -import didKeyEd25519Fixture from './__fixtures__/didKeyEd25519.json' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import didKeyEd25519Fixture from '../../../__tests__/__fixtures__/didKeyEd25519.json' +import { DidKey } from '../DidKey' +import { KeyDidResolver } from '../KeyDidResolver' describe('DidResolver', () => { describe('KeyDidResolver', () => { diff --git a/packages/core/src/modules/dids/methods/key/index.ts b/packages/core/src/modules/dids/methods/key/index.ts new file mode 100644 index 0000000000..c832783193 --- /dev/null +++ b/packages/core/src/modules/dids/methods/key/index.ts @@ -0,0 +1 @@ +export { DidKey } from './DidKey' diff --git a/packages/core/src/modules/dids/methods/peer/DidPeer.ts b/packages/core/src/modules/dids/methods/peer/DidPeer.ts new file mode 100644 index 0000000000..9f25a10223 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/DidPeer.ts @@ -0,0 +1,143 @@ +import type { DidDocument } from '../../domain' +import type { ParsedDid } from '../../types' + +import { hash as sha256 } from '@stablelib/sha256' +import { instanceToInstance } from 'class-transformer' + +import { BufferEncoder, JsonEncoder, MultiBaseEncoder, MultiHashEncoder, Buffer } from '../../../../utils' +import { Key } from '../../domain/Key' +import { getKeyDidMappingByKeyType } from '../../domain/key-type' +import { parseDid } from '../../domain/parse' + +import { didDocumentToNumAlgo2Did, didToNumAlgo2DidDocument } from './peerDidNumAlgo2' + +const PEER_DID_REGEX = new RegExp( + '^did:peer:(([01](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))|(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))+(.(S)[0-9a-zA-Z=]*)?)))$' +) + +export const enum PeerDidNumAlgo { + InceptionKeyWithoutDoc = 0, + GenesisDoc = 1, + MultipleInceptionKeyWithoutDoc = 2, +} + +function getNumAlgoFromPeerDid(did: string) { + return Number(did[9]) +} + +export class DidPeer { + private readonly parsedDid: ParsedDid + + // If numAlgo 1 is used, the did document always has a did document + private readonly _didDocument?: DidDocument + + private constructor({ didDocument, did }: { did: string; didDocument?: DidDocument }) { + const parsed = parseDid(did) + + if (!this.isValidPeerDid(did)) { + throw new Error(`Invalid peer did '${did}'`) + } + + this.parsedDid = parsed + this._didDocument = didDocument + } + + public static fromKey(key: Key) { + const did = `did:peer:0${key.fingerprint}` + return new DidPeer({ did }) + } + + public static fromDid(did: string) { + return new DidPeer({ + did, + }) + } + + public static fromDidDocument( + didDocument: DidDocument, + numAlgo?: PeerDidNumAlgo.GenesisDoc | PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc + ): DidPeer { + if (!numAlgo && didDocument.id.startsWith('did:peer:')) { + numAlgo = getNumAlgoFromPeerDid(didDocument.id) + } + + if (!numAlgo) { + throw new Error( + 'Could not determine numAlgo. The did document must either have a full id property containing the numAlgo, or the numAlgo must be provided as a separate property' + ) + } + + if (numAlgo === PeerDidNumAlgo.GenesisDoc) { + // FIXME: We should do this on the JSON value of the did document, as the DidDocument class + // adds a lot of properties and default values that will mess with the hash value + // Remove id from did document as the id should be generated without an id. + const didDocumentBuffer = JsonEncoder.toBuffer({ ...didDocument.toJSON(), id: undefined }) + + // TODO: we should improve the buffer/multibase/multihash API. + const didIdentifier = BufferEncoder.toUtf8String( + MultiBaseEncoder.encode( + Buffer.from(MultiHashEncoder.encode(sha256(didDocumentBuffer), 'sha2-256')), + 'base58btc' + ) + ) + + const did = `did:peer:1${didIdentifier}` + + return new DidPeer({ did, didDocument }) + } else if (numAlgo === PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc) { + const did = didDocumentToNumAlgo2Did(didDocument) + return new DidPeer({ did }) + } else { + throw new Error(`Unsupported numAlgo: ${numAlgo}. Not all peer did methods support parsing a did document`) + } + } + + public get did() { + return this.parsedDid.did + } + + public get numAlgo(): PeerDidNumAlgo { + // numalgo is the first digit of the method specific identifier + return Number(this.parsedDid.id[0]) as PeerDidNumAlgo + } + + private get identifierWithoutNumAlgo() { + return this.parsedDid.id.substring(1) + } + + private isValidPeerDid(did: string): boolean { + const isValid = PEER_DID_REGEX.test(did) + + return isValid + } + + public get didDocument() { + // Method 1 (numAlgo 0) + if (this.numAlgo === PeerDidNumAlgo.InceptionKeyWithoutDoc) { + const key = Key.fromFingerprint(this.identifierWithoutNumAlgo) + const { getDidDocument } = getKeyDidMappingByKeyType(key.keyType) + + return getDidDocument(this.parsedDid.did, key) + } + // Method 2 (numAlgo 1) + else if (this.numAlgo === PeerDidNumAlgo.GenesisDoc) { + if (!this._didDocument) { + throw new Error('No did document provided for method 1 peer did') + } + + // Clone the document, and set the id + const didDocument = instanceToInstance(this._didDocument) + didDocument.id = this.did + + return didDocument + } + // Method 3 (numAlgo 2) + else if (this.numAlgo === PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc) { + const didDocument = didToNumAlgo2DidDocument(this.parsedDid.did) + + return didDocument + } + + throw new Error(`Unsupported numAlgo '${this.numAlgo}'`) + } +} diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts new file mode 100644 index 0000000000..aea1704f0b --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts @@ -0,0 +1,49 @@ +import type { DidDocument } from '../../domain' +import type { DidResolver } from '../../domain/DidResolver' +import type { DidDocumentRepository } from '../../repository' +import type { DidResolutionResult } from '../../types' + +import { DidPeer, PeerDidNumAlgo } from './DidPeer' + +export class PeerDidResolver implements DidResolver { + public readonly supportedMethods = ['peer'] + + private didDocumentRepository: DidDocumentRepository + + public constructor(didDocumentRepository: DidDocumentRepository) { + this.didDocumentRepository = didDocumentRepository + } + + public async resolve(did: string): Promise { + const didDocumentMetadata = {} + + try { + const didPeer = DidPeer.fromDid(did) + + let didDocument: DidDocument + + // For Method 1, retrieve from storage + if (didPeer.numAlgo === PeerDidNumAlgo.GenesisDoc) { + const didDocumentRecord = await this.didDocumentRepository.getById(did) + didDocument = didDocumentRecord.didDocument + } else { + didDocument = didPeer.didDocument + } + + return { + 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/methods/peer/__tests__/DidPeer.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/DidPeer.test.ts new file mode 100644 index 0000000000..393cd74bd8 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/DidPeer.test.ts @@ -0,0 +1,97 @@ +import { JsonTransformer } from '../../../../../utils' +import didKeyBls12381g1 from '../../../__tests__/__fixtures__/didKeyBls12381g1.json' +import didKeyBls12381g1g2 from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' +import didKeyBls12381g2 from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' +import didKeyEd25519 from '../../../__tests__/__fixtures__/didKeyEd25519.json' +import didKeyX25519 from '../../../__tests__/__fixtures__/didKeyX25519.json' +import { DidDocument, Key } from '../../../domain' +import { DidPeer, PeerDidNumAlgo } from '../DidPeer' + +import didPeer1zQmR from './__fixtures__/didPeer1zQmR.json' +import didPeer1zQmZ from './__fixtures__/didPeer1zQmZ.json' +import didPeer2ez6L from './__fixtures__/didPeer2ez6L.json' + +describe('DidPeer', () => { + test('transforms a key correctly into a peer did method 0 did document', async () => { + const didDocuments = [didKeyEd25519, didKeyBls12381g1, didKeyX25519, didKeyBls12381g1g2, didKeyBls12381g2] + + for (const didDocument of didDocuments) { + const key = Key.fromFingerprint(didDocument.id.split(':')[2]) + + const didPeer = DidPeer.fromKey(key) + const expectedDidPeerDocument = JSON.parse( + JSON.stringify(didDocument).replace(new RegExp('did:key:', 'g'), 'did:peer:0') + ) + + expect(didPeer.didDocument.toJSON()).toMatchObject(expectedDidPeerDocument) + } + }) + + test('transforms a method 2 did correctly into a did document', () => { + expect(DidPeer.fromDid(didPeer2ez6L.id).didDocument.toJSON()).toMatchObject(didPeer2ez6L) + }) + + test('transforms a method 0 did correctly into a did document', () => { + const didDocuments = [didKeyEd25519, didKeyBls12381g1, didKeyX25519, didKeyBls12381g1g2, didKeyBls12381g2] + + for (const didDocument of didDocuments) { + const didPeer = DidPeer.fromDid(didDocument.id.replace('did:key:', 'did:peer:0')) + const expectedDidPeerDocument = JSON.parse( + JSON.stringify(didDocument).replace(new RegExp('did:key:', 'g'), 'did:peer:0') + ) + + expect(didPeer.didDocument.toJSON()).toMatchObject(expectedDidPeerDocument) + } + }) + + test('transforms a did document into a valid method 2 did', () => { + const didPeer2 = DidPeer.fromDidDocument( + JsonTransformer.fromJSON(didPeer2ez6L, DidDocument), + PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc + ) + + expect(didPeer2.did).toBe(didPeer2ez6L.id) + }) + + test('transforms a did document into a valid method 1 did', () => { + const didPeer1 = DidPeer.fromDidDocument( + JsonTransformer.fromJSON(didPeer1zQmR, DidDocument), + PeerDidNumAlgo.GenesisDoc + ) + + expect(didPeer1.did).toBe(didPeer1zQmR.id) + }) + + // FIXME: we need some input data from AFGO for this test to succeed (we create a hash of the document, so any inconsistency is fatal) + xtest('transforms a did document from aries-framework-go into a valid method 1 did', () => { + const didPeer1 = DidPeer.fromDidDocument( + JsonTransformer.fromJSON(didPeer1zQmZ, DidDocument), + PeerDidNumAlgo.GenesisDoc + ) + + expect(didPeer1.did).toBe(didPeer1zQmZ.id) + }) + + test('extracts the numAlgo from the peer did', async () => { + // NumAlgo 0 + const key = Key.fromFingerprint(didKeyEd25519.id.split(':')[2]) + const didPeerNumAlgo0 = DidPeer.fromKey(key) + + expect(didPeerNumAlgo0.numAlgo).toBe(PeerDidNumAlgo.InceptionKeyWithoutDoc) + expect(DidPeer.fromDid('did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL').numAlgo).toBe( + PeerDidNumAlgo.InceptionKeyWithoutDoc + ) + + // NumAlgo 1 + const peerDidNumAlgo1 = 'did:peer:1zQmZMygzYqNwU6Uhmewx5Xepf2VLp5S4HLSwwgf2aiKZuwa' + expect(DidPeer.fromDid(peerDidNumAlgo1).numAlgo).toBe(PeerDidNumAlgo.GenesisDoc) + + // NumAlgo 2 + const peerDidNumAlgo2 = + 'did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0' + expect(DidPeer.fromDid(peerDidNumAlgo2).numAlgo).toBe(PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc) + expect(DidPeer.fromDidDocument(JsonTransformer.fromJSON(didPeer2ez6L, DidDocument)).numAlgo).toBe( + PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc + ) + }) +}) diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmR.json b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmR.json new file mode 100644 index 0000000000..75d2e1d6eb --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmR.json @@ -0,0 +1,32 @@ +{ + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmRYBx1pL86DrsxoJ2ZD3w42d7Ng92ErPgFsCSqg8Q1h4i", + "authentication": [ + { + "id": "#6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7" + }, + { + "id": "#6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ], + "keyAgreement": [ + { + "id": "#6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc", + "type": "X25519KeyAgreementKey2019", + "publicKeyBase58": "JhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr" + } + ], + "service": [ + { + "id": "#service-0", + "type": "DIDCommMessaging", + "serviceEndpoint": "https://example.com/endpoint", + "routingKeys": ["did:example:somemediator#somekey"], + "accept": ["didcomm/v2", "didcomm/aip2;env=rfc587"] + } + ] +} diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmZ.json b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmZ.json new file mode 100644 index 0000000000..17d3d00c53 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmZ.json @@ -0,0 +1,27 @@ +{ + "@context": ["https://w3id.org/did/v1", "https://w3id.org/did/v2"], + "id": "did:peer:1zQmZdT2jawCX5T1RKUB7ro83gQuiKbuHwuHi8G1NypB8BTr", + "verificationMethod": [ + { + "id": "did:example:123456789abcdefghi#keys-1", + "type": "Secp256k1VerificationKey2018", + "controller": "did:example:123456789abcdefghi", + "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + }, + { + "id": "did:example:123456789abcdefghw#key2", + "type": "RsaVerificationKey2018", + "controller": "did:example:123456789abcdefghw", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAryQICCl6NZ5gDKrnSztO\n3Hy8PEUcuyvg/ikC+VcIo2SFFSf18a3IMYldIugqqqZCs4/4uVW3sbdLs/6PfgdX\n7O9D22ZiFWHPYA2k2N744MNiCD1UE+tJyllUhSblK48bn+v1oZHCM0nYQ2NqUkvS\nj+hwUU3RiWl7x3D2s9wSdNt7XUtW05a/FXehsPSiJfKvHJJnGOX0BgTvkLnkAOTd\nOrUZ/wK69Dzu4IvrN4vs9Nes8vbwPa/ddZEzGR0cQMt0JBkhk9kU/qwqUseP1QRJ\n5I1jR4g8aYPL/ke9K35PxZWuDp3U0UPAZ3PjFAh+5T+fc7gzCs9dPzSHloruU+gl\nFQIDAQAB\n-----END PUBLIC KEY-----" + } + ], + "authentication": [ + { + "id": "did:example:123456789abcdefghs#key3", + "type": "RsaVerificationKey2018", + "controller": "did:example:123456789abcdefghs", + "publicKeyHex": "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71" + } + ], + "created": "0001-01-01 00:00:00 +0000 UTC" +} diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer2ez6L.json b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer2ez6L.json new file mode 100644 index 0000000000..6145cc3489 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer2ez6L.json @@ -0,0 +1,35 @@ +{ + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", + "authentication": [ + { + "id": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0#6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", + "type": "Ed25519VerificationKey2018", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", + "publicKeyBase58": "ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7" + }, + { + "id": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0#6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg", + "type": "Ed25519VerificationKey2018", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ], + "keyAgreement": [ + { + "id": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0#6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc", + "type": "X25519KeyAgreementKey2019", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", + "publicKeyBase58": "JhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr" + } + ], + "service": [ + { + "id": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0#service-0", + "type": "DIDCommMessaging", + "serviceEndpoint": "https://example.com/endpoint", + "routingKeys": ["did:example:somemediator#somekey"], + "accept": ["didcomm/v2", "didcomm/aip2;env=rfc587"] + } + ] +} diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo2.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo2.test.ts new file mode 100644 index 0000000000..49f6a69320 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo2.test.ts @@ -0,0 +1,23 @@ +import { JsonTransformer } from '../../../../../utils' +import { DidDocument } from '../../../domain' +import { didToNumAlgo2DidDocument, didDocumentToNumAlgo2Did } from '../peerDidNumAlgo2' + +import didPeer2ez6L from './__fixtures__/didPeer2ez6L.json' + +describe('peerDidNumAlgo2', () => { + describe('didDocumentToNumAlgo2Did', () => { + test('transforms method 2 peer did to a did document', async () => { + expect(didToNumAlgo2DidDocument(didPeer2ez6L.id).toJSON()).toMatchObject(didPeer2ez6L) + }) + }) + + describe('didDocumentToNumAlgo2Did', () => { + test('transforms method 2 peer did document to a did', async () => { + const expectedDid = didPeer2ez6L.id + + const didDocument = JsonTransformer.fromJSON(didPeer2ez6L, DidDocument) + + expect(didDocumentToNumAlgo2Did(didDocument)).toBe(expectedDid) + }) + }) +}) diff --git a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts new file mode 100644 index 0000000000..511a812757 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts @@ -0,0 +1,189 @@ +import type { JsonObject } from '../../../../types' +import type { DidDocument, VerificationMethod } from '../../domain' + +import { JsonEncoder, JsonTransformer } from '../../../../utils' +import { DidDocumentService, Key } from '../../domain' +import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' +import { getKeyDidMappingByKeyType, getKeyDidMappingByVerificationMethod } from '../../domain/key-type' +import { parseDid } from '../../domain/parse' + +enum DidPeerPurpose { + Assertion = 'A', + Encryption = 'E', + Verification = 'V', + CapabilityInvocation = 'I', + CapabilityDelegation = 'D', + Service = 'S', +} + +function isDidPeerKeyPurpose(purpose: string): purpose is Exclude { + return purpose !== DidPeerPurpose.Service && Object.values(DidPeerPurpose).includes(purpose as DidPeerPurpose) +} + +const didPeerAbbreviations: { [key: string]: string | undefined } = { + type: 't', + DIDCommMessaging: 'dm', + serviceEndpoint: 's', + routingKeys: 'r', + accept: 'a', +} + +const didPeerExpansions: { [key: string]: string | undefined } = { + t: 'type', + dm: 'DIDCommMessaging', + s: 'serviceEndpoint', + r: 'routingKeys', + a: 'accept', +} + +export function didToNumAlgo2DidDocument(did: string) { + const parsed = parseDid(did) + const identifierWithoutNumAlgo = parsed.id.substring(2) + + // Get a list of all did document entries splitted by . + const entries = identifierWithoutNumAlgo.split('.') + const didDocument = new DidDocumentBuilder(did) + let serviceIndex = 0 + + for (const entry of entries) { + // Remove the purpose identifier to get the service or key content + const entryContent = entry.substring(1) + // Get the purpose identifier + const purpose = entry[0] + + // Handle service entry first + if (purpose === DidPeerPurpose.Service) { + let service = JsonEncoder.fromBase64(entryContent) + + // Expand abbreviations used for service key/values + service = expandServiceAbbreviations(service) + + // FIXME: Not sure how the service id should be encoded. Using #service- + // for now. See https://github.com/decentralized-identity/peer-did-method-spec/issues/39 + service.id = `${did}#service-${serviceIndex++}` + + didDocument.addService(JsonTransformer.fromJSON(service, DidDocumentService)) + } + // Otherwise we can be sure it is a key + else { + // Decode the fingerprint, and extract the verification method(s) + const key = Key.fromFingerprint(entryContent) + const { getVerificationMethods } = getKeyDidMappingByKeyType(key.keyType) + const verificationMethods = getVerificationMethods(did, key) + + // Add all verification methods to the did document + for (const verificationMethod of verificationMethods) { + // FIXME: the peer did uses key identifiers without the multi base prefix + // However method 0 (and thus did:key) do use the multi base prefix in the + // key identifier. Fixing it like this for now, before making something more complex + verificationMethod.id = verificationMethod.id.replace('#z', '#') + addVerificationMethodToDidDocument(didDocument, verificationMethod, purpose) + } + } + } + + return didDocument.build() +} + +export function didDocumentToNumAlgo2Did(didDocument: DidDocument) { + const purposeMapping = { + [DidPeerPurpose.Assertion]: didDocument.assertionMethod, + [DidPeerPurpose.Encryption]: didDocument.keyAgreement, + // FIXME: should verification be authentication or verificationMethod + // verificationMethod is general so it doesn't make a lot of sense to add + // it to the verificationMethod list + [DidPeerPurpose.Verification]: didDocument.authentication, + [DidPeerPurpose.CapabilityInvocation]: didDocument.capabilityInvocation, + [DidPeerPurpose.CapabilityDelegation]: didDocument.capabilityDelegation, + } + + let did = 'did:peer:2' + + for (const [purpose, entries] of Object.entries(purposeMapping)) { + // Dereference all entries to full verification methods + const dereferenced = entries.map((entry) => (typeof entry === 'string' ? didDocument.dereferenceKey(entry) : entry)) + + // Transform als verification methods into a fingerprint (multibase, multicodec) + const encoded = dereferenced.map((entry) => { + const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(entry) + const key = getKeyFromVerificationMethod(entry) + + // Encode as '.PurposeFingerprint' + const encoded = `.${purpose}${key.fingerprint}` + + return encoded + }) + + // Add all encoded keys + did += encoded.join('') + } + + for (const service of didDocument.service) { + // Transform to JSON, remove id property + const serviceJson = JsonTransformer.toJSON(service) + delete serviceJson.id + + const abbreviatedService = abbreviateServiceJson(serviceJson) + + const encodedService = JsonEncoder.toBase64URL(abbreviatedService) + + did += `.${DidPeerPurpose.Service}${encodedService}` + } + + return did +} + +function expandServiceAbbreviations(service: JsonObject) { + const expand = (abbreviated: string) => didPeerExpansions[abbreviated] ?? abbreviated + + const fullService = Object.entries(service).reduce( + (serviceBody, [key, value]) => ({ + ...serviceBody, + [expand(key)]: expand(value as string), + }), + {} + ) + + return fullService +} + +function abbreviateServiceJson(service: JsonObject) { + const abbreviate = (expanded: string) => didPeerAbbreviations[expanded] ?? expanded + + const abbreviatedService = Object.entries(service).reduce( + (serviceBody, [key, value]) => ({ + ...serviceBody, + [abbreviate(key)]: abbreviate(value as string), + }), + {} + ) + + return abbreviatedService +} + +function addVerificationMethodToDidDocument( + didDocument: DidDocumentBuilder, + verificationMethod: VerificationMethod, + purpose: string +) { + const purposeMapping = { + [DidPeerPurpose.Assertion]: didDocument.addAssertionMethod.bind(didDocument), + [DidPeerPurpose.Encryption]: didDocument.addKeyAgreement.bind(didDocument), + // FIXME: should verification be authentication or verificationMethod + // verificationMethod is general so it doesn't make a lot of sense to add + // it to the verificationMethod list + [DidPeerPurpose.Verification]: didDocument.addAuthentication.bind(didDocument), + [DidPeerPurpose.CapabilityInvocation]: didDocument.addCapabilityInvocation.bind(didDocument), + [DidPeerPurpose.CapabilityDelegation]: didDocument.addCapabilityDelegation.bind(didDocument), + } + + // Verify the purpose is a did peer key purpose (service excluded) + if (isDidPeerKeyPurpose(purpose)) { + const addVerificationMethod = purposeMapping[purpose] + + // Add the verification method based on the method from the mapping + addVerificationMethod(verificationMethod) + } else { + throw new Error(`Unsupported peer did purpose '${purpose}'`) + } +} diff --git a/packages/core/src/modules/dids/resolvers/WebDidResolver.ts b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts similarity index 79% rename from packages/core/src/modules/dids/resolvers/WebDidResolver.ts rename to packages/core/src/modules/dids/methods/web/WebDidResolver.ts index 8855855c3b..ee8642326e 100644 --- a/packages/core/src/modules/dids/resolvers/WebDidResolver.ts +++ b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts @@ -1,12 +1,12 @@ -import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../types' -import type { DidResolver } from './DidResolver' +import type { DidResolver } from '../../domain/DidResolver' +import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../../types' 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' +import { JsonTransformer } from '../../../../utils/JsonTransformer' +import { MessageValidator } from '../../../../utils/MessageValidator' +import { DidDocument } from '../../domain' export class WebDidResolver implements DidResolver { public readonly supportedMethods diff --git a/packages/core/src/modules/dids/parse.ts b/packages/core/src/modules/dids/parse.ts deleted file mode 100644 index 2fba56ef2e..0000000000 --- a/packages/core/src/modules/dids/parse.ts +++ /dev/null @@ -1,7 +0,0 @@ -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/repository/DidDocumentRecord.ts b/packages/core/src/modules/dids/repository/DidDocumentRecord.ts new file mode 100644 index 0000000000..43db2b7b51 --- /dev/null +++ b/packages/core/src/modules/dids/repository/DidDocumentRecord.ts @@ -0,0 +1,52 @@ +import type { TagsBase } from '../../../storage/BaseRecord' + +import { Type } from 'class-transformer' +import { IsEnum, ValidateNested } from 'class-validator' + +import { BaseRecord } from '../../../storage/BaseRecord' +import { DidDocument } from '../domain' +import { DidDocumentRole } from '../domain/DidDocumentRole' + +export interface DidDocumentRecordProps { + id: string + role: DidDocumentRole + didDocument: DidDocument + createdAt?: Date + tags?: CustomDidDocumentTags +} + +export type CustomDidDocumentTags = TagsBase + +export type DefaultDidDocumentTags = TagsBase + +export class DidDocumentRecord + extends BaseRecord + implements DidDocumentRecordProps +{ + @Type(() => DidDocument) + @ValidateNested() + public didDocument!: DidDocument + + @IsEnum(DidDocumentRole) + public role!: DidDocumentRole + + public static readonly type = 'DidDocumentRecord' + public readonly type = DidDocumentRecord.type + + public constructor(props: DidDocumentRecordProps) { + super() + + if (props) { + this.id = props.id + this.role = props.role + this.didDocument = props.didDocument + this.createdAt = props.createdAt ?? new Date() + } + } + + public getTags() { + return { + ...this._tags, + } + } +} diff --git a/packages/core/src/modules/dids/repository/DidDocumentRepository.ts b/packages/core/src/modules/dids/repository/DidDocumentRepository.ts new file mode 100644 index 0000000000..632e1fa50d --- /dev/null +++ b/packages/core/src/modules/dids/repository/DidDocumentRepository.ts @@ -0,0 +1,14 @@ +import { inject, scoped, Lifecycle } from 'tsyringe' + +import { InjectionSymbols } from '../../../constants' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { DidDocumentRecord } from './DidDocumentRecord' + +@scoped(Lifecycle.ContainerScoped) +export class DidDocumentRepository extends Repository { + public constructor(@inject(InjectionSymbols.StorageService) storageService: StorageService) { + super(DidDocumentRecord, storageService) + } +} diff --git a/packages/core/src/modules/dids/repository/index.ts b/packages/core/src/modules/dids/repository/index.ts new file mode 100644 index 0000000000..fda3a2c84e --- /dev/null +++ b/packages/core/src/modules/dids/repository/index.ts @@ -0,0 +1,2 @@ +export * from './DidDocumentRepository' +export * from './DidDocumentRecord' diff --git a/packages/core/src/modules/dids/services/DidResolverService.ts b/packages/core/src/modules/dids/services/DidResolverService.ts index 812ea39435..69175fe204 100644 --- a/packages/core/src/modules/dids/services/DidResolverService.ts +++ b/packages/core/src/modules/dids/services/DidResolverService.ts @@ -1,25 +1,36 @@ import type { Logger } from '../../../logger' -import type { DidResolver } from '../resolvers/DidResolver' +import type { DidResolver } from '../domain/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' +import { parseDid } from '../domain/parse' +import { IndyDidResolver } from '../methods/indy/IndyDidResolver' +import { KeyDidResolver } from '../methods/key/KeyDidResolver' +import { PeerDidResolver } from '../methods/peer/PeerDidResolver' +import { WebDidResolver } from '../methods/web/WebDidResolver' +import { DidDocumentRepository } from '../repository' @scoped(Lifecycle.ContainerScoped) export class DidResolverService { private logger: Logger private resolvers: DidResolver[] - public constructor(agentConfig: AgentConfig, indyLedgerService: IndyLedgerService) { + public constructor( + agentConfig: AgentConfig, + indyLedgerService: IndyLedgerService, + didDocumentRepository: DidDocumentRepository + ) { this.logger = agentConfig.logger - this.resolvers = [new IndyDidResolver(indyLedgerService), new WebDidResolver(), new KeyDidResolver()] + this.resolvers = [ + new IndyDidResolver(indyLedgerService), + new WebDidResolver(), + new KeyDidResolver(), + new PeerDidResolver(didDocumentRepository), + ] } public async resolve(didUrl: string, options: DidResolutionOptions = {}): Promise { @@ -31,8 +42,10 @@ export class DidResolverService { didDocumentMetadata: {}, } - const parsed = parseDid(didUrl) - if (!parsed) { + let parsed: ParsedDid + try { + parsed = parseDid(didUrl) + } catch (error) { return { ...result, didResolutionMetadata: { error: 'invalidDid' }, diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index a67603c605..86cc9445f5 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,3 +1,6 @@ export * from './BufferEncoder' export * from './JsonEncoder' +export * from './JsonTransformer' +export * from './MultiBaseEncoder' export * from './buffer' +export * from './MultiHashEncoder' diff --git a/packages/core/tests/dids.test.ts b/packages/core/tests/dids.test.ts index 34b6178ab9..dfb0920ec2 100644 --- a/packages/core/tests/dids.test.ts +++ b/packages/core/tests/dids.test.ts @@ -25,7 +25,7 @@ describe('dids', () => { expect(JsonTransformer.toJSON(did)).toMatchObject({ didDocument: { '@context': [ - 'https://w3id.org/ns/did/v1', + 'https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1', 'https://w3id.org/security/suites/x25519-2019/v1', ], @@ -66,7 +66,7 @@ describe('dids', () => { expect(JsonTransformer.toJSON(did)).toMatchObject({ didDocument: { '@context': [ - 'https://w3id.org/ns/did/v1', + 'https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1', 'https://w3id.org/security/suites/x25519-2019/v1', ], @@ -80,6 +80,20 @@ describe('dids', () => { controller: 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', publicKeyBase58: '6fioC1zcDPyPEL19pXRS2E4iJ46zH7xP6uSgAaPdwDrx', }, + ], + authentication: [ + 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + ], + assertionMethod: [ + 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + ], + capabilityInvocation: [ + 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + ], + capabilityDelegation: [ + 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + ], + keyAgreement: [ { id: 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6LSrdqo4M24WRDJj1h2hXxgtDTyzjjKCiyapYVgrhwZAySn', type: 'X25519KeyAgreementKey2019', @@ -87,20 +101,55 @@ describe('dids', () => { publicKeyBase58: 'FxfdY3DCQxVZddKGAtSjZdFW9bCCW7oRwZn1NFJ2Tbg2', }, ], + service: [], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should resolve a did:peer did', async () => { + const did = await agent.dids.resolve('did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') + + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + alsoKnownAs: [], + controller: [], + verificationMethod: [ + { + id: 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + type: 'Ed25519VerificationKey2018', + controller: 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + publicKeyBase58: '6fioC1zcDPyPEL19pXRS2E4iJ46zH7xP6uSgAaPdwDrx', + }, + ], authentication: [ - 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', ], assertionMethod: [ - 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', ], capabilityInvocation: [ - 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', ], capabilityDelegation: [ - 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', ], keyAgreement: [ - 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6LSrdqo4M24WRDJj1h2hXxgtDTyzjjKCiyapYVgrhwZAySn', + { + id: 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6LSrdqo4M24WRDJj1h2hXxgtDTyzjjKCiyapYVgrhwZAySn', + type: 'X25519KeyAgreementKey2019', + controller: 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + publicKeyBase58: 'FxfdY3DCQxVZddKGAtSjZdFW9bCCW7oRwZn1NFJ2Tbg2', + }, ], service: [], }, diff --git a/samples/mediator.ts b/samples/mediator.ts index 83f6db65ec..d787405869 100644 --- a/samples/mediator.ts +++ b/samples/mediator.ts @@ -13,6 +13,7 @@ */ import type { InitConfig } from '@aries-framework/core' +import type { Socket } from 'net' import express from 'express' import { Server } from 'ws' @@ -87,7 +88,7 @@ const run = async () => { // When an 'upgrade' to WS is made on our http server, we forward the // request to the WS server httpInboundTransport.server?.on('upgrade', (request, socket, head) => { - socketServer.handleUpgrade(request, socket, head, (socket) => { + socketServer.handleUpgrade(request, socket as Socket, head, (socket) => { socketServer.emit('connection', socket, request) }) })