Skip to content

Commit

Permalink
feat(indy-vdr): did:sov resolver (#1247)
Browse files Browse the repository at this point in the history
Signed-off-by: Ariel Gentile <[email protected]>
  • Loading branch information
genaris authored Jan 30, 2023
1 parent 13f3740 commit b5eb08e
Show file tree
Hide file tree
Showing 12 changed files with 725 additions and 2 deletions.
95 changes: 95 additions & 0 deletions packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { GetNymResponseData, IndyEndpointAttrib } from './didSovUtil'
import type { DidResolutionResult, ParsedDid, DidResolver, AgentContext } from '@aries-framework/core'

import { GetAttribRequest, GetNymRequest } from 'indy-vdr-test-shared'

import { IndyVdrError, IndyVdrNotFoundError } from '../error'
import { IndyVdrPoolService } from '../pool'

import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovUtil'

export class IndyVdrSovDidResolver implements DidResolver {
public readonly supportedMethods = ['sov']

public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise<DidResolutionResult> {
const didDocumentMetadata = {}

try {
const nym = await this.getPublicDid(agentContext, parsed.id)
const endpoints = await this.getEndpointsForDid(agentContext, parsed.id)

const keyAgreementId = `${parsed.did}#key-agreement-1`
const builder = sovDidDocumentFromDid(parsed.did, nym.verkey)
addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId)

return {
didDocument: builder.build(),
didDocumentMetadata,
didResolutionMetadata: { contentType: 'application/did+ld+json' },
}
} catch (error) {
return {
didDocument: null,
didDocumentMetadata,
didResolutionMetadata: {
error: 'notFound',
message: `resolver_error: Unable to resolve did '${did}': ${error}`,
},
}
}
}

private async getPublicDid(agentContext: AgentContext, did: string) {
const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService)

const pool = await indyVdrPoolService.getPoolForDid(agentContext, did)

const request = new GetNymRequest({ dest: did })

const didResponse = await pool.submitReadRequest(request)

if (!didResponse.result.data) {
throw new IndyVdrNotFoundError(`DID ${did} not found`)
}
return JSON.parse(didResponse.result.data) as GetNymResponseData
}

private async getEndpointsForDid(agentContext: AgentContext, did: string) {
const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService)

const pool = await indyVdrPoolService.getPoolForDid(agentContext, did)

try {
agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.indyNamespace}'`)

const request = new GetAttribRequest({ targetDid: did, raw: 'endpoint' })

agentContext.config.logger.debug(
`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.indyNamespace}'`
)
const response = await pool.submitReadRequest(request)

if (!response.result.data) return {}

const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib
agentContext.config.logger.debug(
`Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.indyNamespace}'`,
{
response,
endpoints,
}
)

return endpoints ?? {}
} catch (error) {
agentContext.config.logger.error(
`Error retrieving endpoints for did '${did}' from ledger '${pool.indyNamespace}'`,
{
error,
}
)

throw new IndyVdrError(error)
}
}
}
127 changes: 127 additions & 0 deletions packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { JsonTransformer } from '@aries-framework/core'

import { parseDid } from '../../../../core/src/modules/dids/domain/parse'
import { getAgentConfig, getAgentContext, mockProperty } from '../../../../core/tests/helpers'
import { IndyVdrPool, IndyVdrPoolService } from '../../pool'
import { IndyVdrSovDidResolver } from '../IndyVdrSovDidResolver'

import didSovR1xKJw17sUoXhejEpugMYJFixture from './__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json'
import didSovWJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json'

jest.mock('../../pool/IndyVdrPoolService')
const IndyVdrPoolServiceMock = IndyVdrPoolService as jest.Mock<IndyVdrPoolService>
const poolServiceMock = new IndyVdrPoolServiceMock()

jest.mock('../../pool/IndyVdrPool')
const IndyVdrPoolMock = IndyVdrPool as jest.Mock<IndyVdrPool>
const poolMock = new IndyVdrPoolMock()
mockProperty(poolMock, 'indyNamespace', 'local')
jest.spyOn(poolServiceMock, 'getPoolForDid').mockResolvedValue(poolMock)

const agentConfig = getAgentConfig('IndyVdrSovDidResolver')

const agentContext = getAgentContext({
agentConfig,
registerInstances: [[IndyVdrPoolService, poolServiceMock]],
})

const resolver = new IndyVdrSovDidResolver()

describe('DidResolver', () => {
describe('IndyVdrSovDidResolver', () => {
it('should correctly resolve a did:sov document', async () => {
const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ'

const nymResponse = {
result: {
data: JSON.stringify({
did: 'R1xKJw17sUoXhejEpugMYJ',
verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu',
role: 'ENDORSER',
}),
},
}

const attribResponse = {
result: {
data: JSON.stringify({
endpoint: {
endpoint: 'https://ssi.com',
profile: 'https://profile.com',
hub: 'https://hub.com',
},
}),
},
}

jest.spyOn(poolMock, 'submitReadRequest').mockResolvedValueOnce(nymResponse)
jest.spyOn(poolMock, 'submitReadRequest').mockResolvedValueOnce(attribResponse)

const result = await resolver.resolve(agentContext, did, parseDid(did))

expect(JsonTransformer.toJSON(result)).toMatchObject({
didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture,
didDocumentMetadata: {},
didResolutionMetadata: {
contentType: 'application/did+ld+json',
},
})
})

it('should resolve a did:sov document with routingKeys and types entries in the attrib', async () => {
const did = 'did:sov:WJz9mHyW9BZksioQnRsrAo'

const nymResponse = {
result: {
data: JSON.stringify({
did: 'WJz9mHyW9BZksioQnRsrAo',
verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8',
role: 'ENDORSER',
}),
},
}

const attribResponse = {
result: {
data: JSON.stringify({
endpoint: {
endpoint: 'https://agent.com',
types: ['endpoint', 'did-communication', 'DIDComm'],
routingKeys: ['routingKey1', 'routingKey2'],
},
}),
},
}

jest.spyOn(poolMock, 'submitReadRequest').mockResolvedValueOnce(nymResponse)
jest.spyOn(poolMock, 'submitReadRequest').mockResolvedValueOnce(attribResponse)

const result = await resolver.resolve(agentContext, did, parseDid(did))

expect(JsonTransformer.toJSON(result)).toMatchObject({
didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture,
didDocumentMetadata: {},
didResolutionMetadata: {
contentType: 'application/did+ld+json',
},
})
})

it('should return did resolution metadata with error if the indy ledger service throws an error', async () => {
const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ'

jest.spyOn(poolMock, 'submitReadRequest').mockRejectedValue(new Error('Error submitting read request'))

const result = await resolver.resolve(agentContext, did, parseDid(did))

expect(result).toMatchObject({
didDocument: null,
didDocumentMetadata: {},
didResolutionMetadata: {
error: 'notFound',
message: `resolver_error: Unable to resolve did 'did:sov:R1xKJw17sUoXhejEpugMYJ': Error: Error submitting read request`,
},
})
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"@context": [
"https://w3id.org/did/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:sov:R1xKJw17sUoXhejEpugMYJ",
"verificationMethod": [
{
"type": "Ed25519VerificationKey2018",
"controller": "did:sov:R1xKJw17sUoXhejEpugMYJ",
"id": "did:sov:R1xKJw17sUoXhejEpugMYJ#key-1",
"publicKeyBase58": "E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu"
},
{
"type": "X25519KeyAgreementKey2019",
"controller": "did:sov:R1xKJw17sUoXhejEpugMYJ",
"id": "did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1",
"publicKeyBase58": "Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt"
}
],
"authentication": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-1"],
"assertionMethod": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-1"],
"keyAgreement": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"],
"service": [
{
"id": "did:sov:R1xKJw17sUoXhejEpugMYJ#endpoint",
"type": "endpoint",
"serviceEndpoint": "https://ssi.com"
},
{
"accept": ["didcomm/aip2;env=rfc19"],
"id": "did:sov:R1xKJw17sUoXhejEpugMYJ#did-communication",
"priority": 0,
"recipientKeys": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"],
"routingKeys": [],
"serviceEndpoint": "https://ssi.com",
"type": "did-communication"
},
{
"id": "did:sov:R1xKJw17sUoXhejEpugMYJ#profile",
"serviceEndpoint": "https://profile.com",
"type": "profile"
},
{
"id": "did:sov:R1xKJw17sUoXhejEpugMYJ#hub",
"serviceEndpoint": "https://hub.com",
"type": "hub"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"@context": [
"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"
],
"id": "did:sov:WJz9mHyW9BZksioQnRsrAo",
"verificationMethod": [
{
"type": "Ed25519VerificationKey2018",
"controller": "did:sov:WJz9mHyW9BZksioQnRsrAo",
"id": "did:sov:WJz9mHyW9BZksioQnRsrAo#key-1",
"publicKeyBase58": "GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8"
},
{
"type": "X25519KeyAgreementKey2019",
"controller": "did:sov:WJz9mHyW9BZksioQnRsrAo",
"id": "did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1",
"publicKeyBase58": "S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud"
}
],
"authentication": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-1"],
"assertionMethod": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-1"],
"keyAgreement": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"],
"service": [
{
"id": "did:sov:WJz9mHyW9BZksioQnRsrAo#endpoint",
"type": "endpoint",
"serviceEndpoint": "https://agent.com"
},
{
"id": "did:sov:WJz9mHyW9BZksioQnRsrAo#did-communication",
"type": "did-communication",
"priority": 0,
"recipientKeys": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"],
"routingKeys": ["routingKey1", "routingKey2"],
"accept": ["didcomm/aip2;env=rfc19"],
"serviceEndpoint": "https://agent.com"
},
{
"id": "did:sov:WJz9mHyW9BZksioQnRsrAo#didcomm-1",
"type": "DIDComm",
"serviceEndpoint": "https://agent.com",
"accept": ["didcomm/v2"],
"routingKeys": ["routingKey1", "routingKey2"]
}
]
}
Loading

0 comments on commit b5eb08e

Please sign in to comment.