From c03c8e6e2f0e67cc40d9f0392c82805003b1c791 Mon Sep 17 00:00:00 2001 From: Andraz <69682837+andyv09@users.noreply.github.com> Date: Fri, 13 Jan 2023 12:53:23 +0100 Subject: [PATCH] feat: add resolveDID RPC method (#78) * feat: add resolveDID --- packages/connector/src/methods.ts | 16 +- packages/connector/src/snap.ts | 3 +- packages/snap/index.html | 821 ++++++++++--------- packages/snap/snap.manifest.json | 2 +- packages/snap/src/index.ts | 5 + packages/snap/src/rpc/did/resolveDID.ts | 6 + packages/snap/src/utils/didUtils.ts | 9 + packages/snap/src/utils/params.ts | 18 + packages/snap/tests/rpc/onRpcRequest.spec.ts | 22 +- packages/snap/tests/testUtils/constants.ts | 92 +++ packages/snap/tests/utils/didUtils.spec.ts | 35 + packages/snap/tests/utils/params.spec.ts | 6 + packages/types/src/methods.ts | 6 + packages/types/src/params.ts | 4 + packages/types/src/requests.ts | 4 +- packages/types/src/snapApi.ts | 8 +- 16 files changed, 651 insertions(+), 406 deletions(-) create mode 100644 packages/snap/src/rpc/did/resolveDID.ts diff --git a/packages/connector/src/methods.ts b/packages/connector/src/methods.ts index 524ca9320..7d65bc8de 100644 --- a/packages/connector/src/methods.ts +++ b/packages/connector/src/methods.ts @@ -1,4 +1,8 @@ -import { VerifiablePresentation, W3CVerifiableCredential } from '@veramo/core'; +import { + DIDResolutionResult, + VerifiablePresentation, + W3CVerifiableCredential, +} from '@veramo/core'; import { AvailableMethods, AvailableVCStores, @@ -176,3 +180,13 @@ export async function getSnapSettings( ): Promise { return await sendSnapMethod({ method: 'getSnapSettings' }, this.snapId); } + +export async function resolveDID( + this: MetaMaskSSISnap, + did: string +): Promise { + return await sendSnapMethod( + { method: 'resolveDID', params: { did } }, + this.snapId + ); +} diff --git a/packages/connector/src/snap.ts b/packages/connector/src/snap.ts index e95770a1a..4c40b2304 100644 --- a/packages/connector/src/snap.ts +++ b/packages/connector/src/snap.ts @@ -18,6 +18,7 @@ import { deleteVC, getSnapSettings, getAccountSettings, + resolveDID, } from './methods'; export class MetaMaskSSISnap { @@ -57,9 +58,9 @@ export class MetaMaskSSISnap { setVCStore: setVCStore.bind(this), getAvailableVCStores: getAvailableVCStores.bind(this), deleteVC: deleteVC.bind(this), - getSnapSettings: getSnapSettings.bind(this), getAccountSettings: getAccountSettings.bind(this), + resolveDID: resolveDID.bind(this), }; }; } diff --git a/packages/snap/index.html b/packages/snap/index.html index e47e15619..05098311d 100644 --- a/packages/snap/index.html +++ b/packages/snap/index.html @@ -1,459 +1,482 @@ - - Hello, Snaps! - - - - -

Hello, Snaps!

-
- Instructions + + + Hello, Snaps! + + + + +

Hello, Snaps!

+
+ Instructions +
    +
  • First, click "Connect". Then, try out the other buttons!
  • +
  • Please note that:
    • -
    • First, click "Connect". Then, try out the other buttons!
    • -
    • Please note that:
    • -
        -
      • - If you want to generate a VP you need a VC ID. To get it, you need a - VC saved in MetaMask state (use Save VC). Then you need to retrieve - id of VC by using Get VCS button and searching for "key" property in - the console. -
      • -
      +
    • + If you want to generate a VP you need a VC ID. To get it, you need a + VC saved in MetaMask state (use Save VC). Then you need to retrieve + id of VC by using Get VCS button and searching for "key" property in + the console. +
    -
-
- - -
-
- -
-
- -
- - -
-
- - With challenge - - - -
-
- - -
-
- - - -
-
- -
-
- - -
-
- - - - - - + - + } + + + \ No newline at end of file diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 91debf7a9..c3e033020 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/blockchain-lab-um/ssi-snap.git" }, "source": { - "shasum": "aQLEfsHsTGkJA4QLLIxkZXSo1i44PWqcSBT8lN/zskM=", + "shasum": "FgXdqMt9/aWmUN652zOj8sTOmyr9tyVioZz9lFI6rb0=", "location": { "npm": { "filePath": "dist/snap.js", diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts index 113ad8375..f7fc5eb4b 100644 --- a/packages/snap/src/index.ts +++ b/packages/snap/src/index.ts @@ -12,6 +12,7 @@ import { isValidSwitchMethodRequest, isValidDeleteVCRequest, isValidQueryRequest, + isValidResolveDIDRequest, } from './utils/params'; import { switchMethod } from './rpc/did/switchMethod'; import { getDid } from './rpc/did/getDID'; @@ -28,6 +29,7 @@ import { getCurrentAccount } from './utils/snapUtils'; import { getAddressKeyDeriver } from './utils/keyPair'; import { ApiParams } from './interfaces'; import { deleteVC } from './rpc/vc/deleteVC'; +import { resolveDID } from './rpc/did/resolveDID'; export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { let state = await getSnapStateUnchecked(snap); @@ -98,6 +100,9 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { apiParams.state ); return await deleteVC(apiParams, request.params); + case 'resolveDID': + isValidResolveDIDRequest(request.params); + return await resolveDID(request.params.did); default: throw new Error('Method not found.'); } diff --git a/packages/snap/src/rpc/did/resolveDID.ts b/packages/snap/src/rpc/did/resolveDID.ts new file mode 100644 index 000000000..810e30533 --- /dev/null +++ b/packages/snap/src/rpc/did/resolveDID.ts @@ -0,0 +1,6 @@ +import { resolveDid } from '../../utils/didUtils'; + +export async function resolveDID(did: string) { + if (did === '') return { message: 'DID is empty' }; + return await resolveDid(did); +} diff --git a/packages/snap/src/utils/didUtils.ts b/packages/snap/src/utils/didUtils.ts index b657ca403..19bbd5f24 100644 --- a/packages/snap/src/utils/didUtils.ts +++ b/packages/snap/src/utils/didUtils.ts @@ -7,6 +7,7 @@ import { getDidKeyIdentifier } from '../did/key/keyDidUtils'; import { SSISnapState } from '../interfaces'; import { getCurrentNetwork } from './snapUtils'; import { updateSnapState } from './stateUtils'; +import { DIDDocument, DIDResolutionResult } from 'did-resolver'; export async function changeCurrentVCStore( snap: SnapsGlobalObject, @@ -48,3 +49,11 @@ export async function changeCurrentMethod( } return ''; } + +export async function resolveDid(did: string): Promise { + const response = await fetch( + `https://dev.uniresolver.io/1.0/identifiers/${did}` + ); + const data = (await response.json()) as DIDResolutionResult; + return data; +} diff --git a/packages/snap/src/utils/params.ts b/packages/snap/src/utils/params.ts index 8791bbc64..9ed1d8d1f 100644 --- a/packages/snap/src/utils/params.ts +++ b/packages/snap/src/utils/params.ts @@ -13,6 +13,7 @@ import { SetVCStoreRequestParams, DeleteVCsRequestParams, AvailableVCStores, + ResolveDIDRequestParams, } from '@blockchain-lab-um/ssi-snap-types'; import { SSISnapState } from 'src/interfaces'; import { isEnabledVCStore } from './snapUtils'; @@ -327,3 +328,20 @@ export function isValidQueryRequest( } return; } + +export function isValidResolveDIDRequest( + params: unknown +): asserts params is ResolveDIDRequestParams { + const param = params as ResolveDIDRequestParams; + if ( + param !== null && + typeof param === 'object' && + 'did' in param && + param.did !== null && + param.did !== '' && + typeof param.did === 'string' + ) + return; + + throw new Error('Invalid ResolveDID request'); +} diff --git a/packages/snap/tests/rpc/onRpcRequest.spec.ts b/packages/snap/tests/rpc/onRpcRequest.spec.ts index 5ad605d8a..198c73ca4 100644 --- a/packages/snap/tests/rpc/onRpcRequest.spec.ts +++ b/packages/snap/tests/rpc/onRpcRequest.spec.ts @@ -8,12 +8,17 @@ import { getDefaultSnapState, jsonPath, address, + exampleDIDDocument, } from '../testUtils/constants'; import { availableVCStores, availableMethods, } from '@blockchain-lab-um/ssi-snap-types'; -import { IVerifyResult, VerifiablePresentation } from '@veramo/core'; +import { + DIDResolutionResult, + IVerifyResult, + VerifiablePresentation, +} from '@veramo/core'; import * as uuid from 'uuid'; import { getAgent } from '../../src/veramo/setup'; import { veramoClearVCs } from '../../src/utils/veramoUtils'; @@ -1252,4 +1257,19 @@ describe('onRpcRequest', () => { expect.assertions(1); }); }); + describe('resolveDID', () => { + it('should succeed resolving did:ethr', async () => { + const res = (await onRpcRequest({ + origin: 'localhost', + request: { + id: 'test-id', + jsonrpc: '2.0', + method: 'resolveDID', + params: { did: exampleDID }, + }, + })) as DIDResolutionResult; + expect(res.didDocument).toEqual(exampleDIDDocument); + expect.assertions(1); + }); + }); }); diff --git a/packages/snap/tests/testUtils/constants.ts b/packages/snap/tests/testUtils/constants.ts index 6631e776e..2ba78e5fc 100644 --- a/packages/snap/tests/testUtils/constants.ts +++ b/packages/snap/tests/testUtils/constants.ts @@ -49,6 +49,98 @@ export const exampleImportedDID: IIdentifier = { services: [], }; +export const exampleDIDDocument: DIDDocument = { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/secp256k1recovery-2020/v2', + ], + id: 'did:ethr:0x5:0xb6665128eE91D84590f70c3268765384A9CAfBCd', + verificationMethod: [ + { + id: 'did:ethr:0x5:0xb6665128eE91D84590f70c3268765384A9CAfBCd#controller', + type: 'EcdsaSecp256k1RecoveryMethod2020', + controller: 'did:ethr:0x5:0xb6665128eE91D84590f70c3268765384A9CAfBCd', + blockchainAccountId: + 'eip155:5:0xb6665128eE91D84590f70c3268765384A9CAfBCd', + }, + ], + authentication: [ + 'did:ethr:0x5:0xb6665128eE91D84590f70c3268765384A9CAfBCd#controller', + ], + assertionMethod: [ + 'did:ethr:0x5:0xb6665128eE91D84590f70c3268765384A9CAfBCd#controller', + ], +}; + +export const resolutionNotFound = { + '@context': 'https://w3id.org/did-resolution/v1', + didDocument: null, + didResolutionMetadata: { + error: 'notFound', + errorMessage: '404 Not Found (notFound)', + contentType: 'application/did+ld+json', + }, + didDocumentMetadata: {}, +}; + +export const resolutionMethodNotSupported = { + '@context': 'https://w3id.org/did-resolution/v1', + didDocument: null, + didResolutionMetadata: { + error: 'methodNotSupported', + errorMessage: 'Method not supported: keyclopse', + contentType: 'application/did+ld+json', + }, + didDocumentMetadata: {}, +}; + +export const resolutionInvalidDID = { + '@context': 'https://w3id.org/did-resolution/v1', + didDocument: null, + didResolutionMetadata: { + error: 'invalidDid', + message: 'Not a valid did:ethr: 0x5:0x123', + contentType: 'application/did+ld+json', + convertedFrom: 'application/did+json', + convertedTo: 'application/did+ld+json', + }, + didDocumentMetadata: {}, +}; + +export const exampleDIDKeyDocumentUniResovler = { + '@context': [ + 'https://www.w3.org/ns/did/v1', + { + EcdsaSecp256k1VerificationKey2019: + 'https://w3id.org/security#EcdsaSecp256k1VerificationKey2019', + publicKeyJwk: { + '@id': 'https://w3id.org/security#publicKeyJwk', + '@type': '@json', + }, + }, + ], + id: 'did:key:zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik', + verificationMethod: [ + { + id: 'did:key:zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik#zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik', + type: 'EcdsaSecp256k1VerificationKey2019', + controller: 'did:key:zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik', + publicKeyJwk: { + kty: 'EC', + crv: 'secp256k1', + x: 'gKnNSP1Db4wfgbFW62FWGM1XPD6x5tk3oXuCIgJ8roU', + y: 'Cp9WHUFAAai979txPGGdLK8IoMllWwz0LeBlvFHgFpo', + }, + }, + ], + authentication: [ + 'did:key:zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik#zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik', + ], + assertionMethod: [ + 'did:key:zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik#zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik', + ], +}; + export const exampleDIDKeyDocument: DIDDocument = { id: 'did:key:zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik#zQ3shW537fJMvkiw69S1FLvBaE8pyzAx4agHu6iaYzTCejuik', '@context': [ diff --git a/packages/snap/tests/utils/didUtils.spec.ts b/packages/snap/tests/utils/didUtils.spec.ts index 9b5f9da63..ce29cb71d 100644 --- a/packages/snap/tests/utils/didUtils.spec.ts +++ b/packages/snap/tests/utils/didUtils.spec.ts @@ -3,11 +3,18 @@ import { changeCurrentMethod, changeCurrentVCStore, getCurrentDid, + resolveDid, } from '../../src/utils/didUtils'; import { address, exampleDIDKey, getDefaultSnapState, + exampleDID, + exampleDIDKeyDocumentUniResovler, + exampleDIDDocument, + resolutionInvalidDID, + resolutionNotFound, + resolutionMethodNotSupported, } from '../testUtils/constants'; import { createMockSnap, SnapMock } from '../testUtils/snap.mock'; import * as snapUtils from '../../src/utils/snapUtils'; @@ -129,5 +136,33 @@ describe('Utils [did]', () => { expect.assertions(2); }); + + describe('resolveDID', () => { + it('should succeed resolving did:ethr identifier', async () => { + const didDoc = await resolveDid(exampleDID); + expect(didDoc.didDocument).toEqual(exampleDIDDocument); + expect.assertions(1); + }); + it('should succeed resolving did:key identifier', async () => { + const didDoc = await resolveDid(exampleDIDKey); + expect(didDoc.didDocument).toEqual(exampleDIDKeyDocumentUniResovler); + expect.assertions(1); + }); + it('should resolve invalid did', async () => { + const didDoc = await resolveDid('did:ethr:0x5:0x123'); + expect(didDoc).toEqual(resolutionInvalidDID); + expect.assertions(1); + }); + it('should resolve nonExisting did', async () => { + const didDoc = await resolveDid('did:key:zQ3shW537'); + expect(didDoc).toEqual(resolutionNotFound); + expect.assertions(1); + }); + it('should resolve methodNotSupported', async () => { + const didDoc = await resolveDid('did:keyclopse:zQ3shW537'); + expect(didDoc).toEqual(resolutionMethodNotSupported); + expect.assertions(1); + }); + }); }); }); diff --git a/packages/snap/tests/utils/params.spec.ts b/packages/snap/tests/utils/params.spec.ts index 1f3e0d57a..f9595f397 100644 --- a/packages/snap/tests/utils/params.spec.ts +++ b/packages/snap/tests/utils/params.spec.ts @@ -8,6 +8,7 @@ import { isValidCreateVPRequest, isValidDeleteVCRequest, isValidQueryRequest, + isValidResolveDIDRequest, isValidSaveVCRequest, isValidSwitchMethodRequest, } from '../../src/utils/params'; @@ -19,6 +20,11 @@ describe('Utils [params]', () => { describe('isValidGetVCsRequest', () => { // TODO }); + describe('isValidResolveDIDRequest', () => { + it('should fail for null', () => { + expect(() => isValidResolveDIDRequest(null)).toThrow(Error); + }); + }); describe('isValidQueryRequest', () => { it('should fail for not enabled store', () => { diff --git a/packages/types/src/methods.ts b/packages/types/src/methods.ts index 39f9a89a2..2b611c12b 100644 --- a/packages/types/src/methods.ts +++ b/packages/types/src/methods.ts @@ -3,6 +3,7 @@ import { CreateVPRequestParams, DeleteVCsRequestParams, QueryVCsRequestParams, + ResolveDIDRequestParams, SaveVCRequestParams, SetVCStoreRequestParams, SwitchMethodRequestParams, @@ -74,3 +75,8 @@ export type GetSnapSettings = { export type GetAvailableVCStores = { method: 'getAvailableVCStores'; }; + +export type ResolveDID = { + method: 'resolveDID'; + params: ResolveDIDRequestParams; +}; diff --git a/packages/types/src/params.ts b/packages/types/src/params.ts index d1789058c..bcd4c268c 100644 --- a/packages/types/src/params.ts +++ b/packages/types/src/params.ts @@ -68,6 +68,10 @@ export type ChangeInfuraTokenRequestParams = { infuraToken: string; }; +export type ResolveDIDRequestParams = { + did: string; +}; + export type SwitchMethodRequestParams = { didMethod: AvailableMethods; }; diff --git a/packages/types/src/requests.ts b/packages/types/src/requests.ts index 6e0c27427..f5954d37f 100644 --- a/packages/types/src/requests.ts +++ b/packages/types/src/requests.ts @@ -14,6 +14,7 @@ import { GetAvailableVCStores, GetAccountSettings, GetSnapSettings, + ResolveDID, } from './methods'; export type MetaMaskSSISnapRPCRequest = @@ -31,7 +32,8 @@ export type MetaMaskSSISnapRPCRequest = | SetVCStore | GetAvailableVCStores | GetAccountSettings - | GetSnapSettings; + | GetSnapSettings + | ResolveDID; export type Method = MetaMaskSSISnapRPCRequest['method']; diff --git a/packages/types/src/snapApi.ts b/packages/types/src/snapApi.ts index 4e4d3181a..cb10aaa37 100644 --- a/packages/types/src/snapApi.ts +++ b/packages/types/src/snapApi.ts @@ -1,4 +1,8 @@ -import { VerifiablePresentation, W3CVerifiableCredential } from '@veramo/core'; +import { + DIDResolutionResult, + VerifiablePresentation, + W3CVerifiableCredential, +} from '@veramo/core'; import { AvailableMethods, AvailableVCStores } from './constants'; import { CreateVPRequestParams, @@ -30,7 +34,7 @@ export interface SSISnapApi { setVCStore(store: AvailableVCStores, value: boolean): Promise; getAvailableVCStores(): Promise; deleteVC(id: string, options?: DeleteVCsOptions): Promise; - getAccountSettings(): Promise; getSnapSettings(): Promise; + resolveDID(did: string): Promise; }