Skip to content

Commit

Permalink
feat: OOB public did (#930)
Browse files Browse the repository at this point in the history
Signed-off-by: Pavel Zarecky <[email protected]>
  • Loading branch information
Iskander508 authored Aug 29, 2022
1 parent f90a27b commit c99f3c9
Show file tree
Hide file tree
Showing 27 changed files with 537 additions and 184 deletions.
74 changes: 16 additions & 58 deletions packages/core/src/agent/MessageSender.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ConnectionRecord } from '../modules/connections'
import type { ResolvedDidCommService } from '../modules/didcomm'
import type { DidDocument, Key } from '../modules/dids'
import type { OutOfBandRecord } from '../modules/oob/repository'
import type { OutboundTransport } from '../transport/OutboundTransport'
Expand All @@ -11,11 +12,11 @@ import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants'
import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator'
import { AriesFrameworkError } from '../error'
import { Logger } from '../logger'
import { keyReferenceToKey } from '../modules/dids'
import { DidCommDocumentService } from '../modules/didcomm'
import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type'
import { DidCommV1Service, IndyAgentService } from '../modules/dids/domain/service'
import { didKeyToInstanceOfKey, verkeyToInstanceOfKey } from '../modules/dids/helpers'
import { didKeyToInstanceOfKey } from '../modules/dids/helpers'
import { DidResolverService } from '../modules/dids/services/DidResolverService'
import { OutOfBandRepository } from '../modules/oob/repository'
import { inject, injectable } from '../plugins'
import { MessageRepository } from '../storage/MessageRepository'
import { MessageValidator } from '../utils/MessageValidator'
Expand All @@ -24,13 +25,6 @@ import { getProtocolScheme } from '../utils/uri'
import { EnvelopeService } from './EnvelopeService'
import { TransportService } from './TransportService'

export interface ResolvedDidCommService {
id: string
serviceEndpoint: string
recipientKeys: Key[]
routingKeys: Key[]
}

export interface TransportPriorityOptions {
schemes: string[]
restrictive?: boolean
Expand All @@ -43,20 +37,26 @@ export class MessageSender {
private messageRepository: MessageRepository
private logger: Logger
private didResolverService: DidResolverService
private didCommDocumentService: DidCommDocumentService
private outOfBandRepository: OutOfBandRepository
public readonly outboundTransports: OutboundTransport[] = []

public constructor(
envelopeService: EnvelopeService,
transportService: TransportService,
@inject(InjectionSymbols.MessageRepository) messageRepository: MessageRepository,
@inject(InjectionSymbols.Logger) logger: Logger,
didResolverService: DidResolverService
didResolverService: DidResolverService,
didCommDocumentService: DidCommDocumentService,
outOfBandRepository: OutOfBandRepository
) {
this.envelopeService = envelopeService
this.transportService = transportService
this.messageRepository = messageRepository
this.logger = logger
this.didResolverService = didResolverService
this.didCommDocumentService = didCommDocumentService
this.outOfBandRepository = outOfBandRepository
this.outboundTransports = []
}

Expand Down Expand Up @@ -342,49 +342,6 @@ export class MessageSender {
throw new AriesFrameworkError(`Unable to send message to service: ${service.serviceEndpoint}`)
}

private async retrieveServicesFromDid(did: string) {
this.logger.debug(`Resolving services for did ${did}.`)
const didDocument = await this.didResolverService.resolveDidDocument(did)

const didCommServices: ResolvedDidCommService[] = []

// FIXME: we currently retrieve did documents for all didcomm services in the did document, and we don't have caching
// yet so this will re-trigger ledger resolves for each one. Should we only resolve the first service, then the second service, etc...?
for (const didCommService of didDocument.didCommServices) {
if (didCommService instanceof IndyAgentService) {
// IndyAgentService (DidComm v0) has keys encoded as raw publicKeyBase58 (verkeys)
didCommServices.push({
id: didCommService.id,
recipientKeys: didCommService.recipientKeys.map(verkeyToInstanceOfKey),
routingKeys: didCommService.routingKeys?.map(verkeyToInstanceOfKey) || [],
serviceEndpoint: didCommService.serviceEndpoint,
})
} else if (didCommService instanceof DidCommV1Service) {
// Resolve dids to DIDDocs to retrieve routingKeys
const routingKeys = []
for (const routingKey of didCommService.routingKeys ?? []) {
const routingDidDocument = await this.didResolverService.resolveDidDocument(routingKey)
routingKeys.push(keyReferenceToKey(routingDidDocument, routingKey))
}

// Dereference recipientKeys
const recipientKeys = didCommService.recipientKeys.map((recipientKey) =>
keyReferenceToKey(didDocument, recipientKey)
)

// DidCommV1Service has keys encoded as key references
didCommServices.push({
id: didCommService.id,
recipientKeys,
routingKeys,
serviceEndpoint: didCommService.serviceEndpoint,
})
}
}

return didCommServices
}

private async retrieveServicesByConnection(
connection: ConnectionRecord,
transportPriority?: TransportPriorityOptions,
Expand All @@ -399,14 +356,15 @@ export class MessageSender {

if (connection.theirDid) {
this.logger.debug(`Resolving services for connection theirDid ${connection.theirDid}.`)
didCommServices = await this.retrieveServicesFromDid(connection.theirDid)
didCommServices = await this.didCommDocumentService.resolveServicesFromDid(connection.theirDid)
} else if (outOfBand) {
this.logger.debug(`Resolving services from out-of-band record ${outOfBand?.id}.`)
this.logger.debug(`Resolving services from out-of-band record ${outOfBand.id}.`)
if (connection.isRequester) {
for (const service of outOfBand.outOfBandInvitation.services) {
for (const service of outOfBand.outOfBandInvitation.getServices()) {
// Resolve dids to DIDDocs to retrieve services
if (typeof service === 'string') {
didCommServices = await this.retrieveServicesFromDid(service)
this.logger.debug(`Resolving services for did ${service}.`)
didCommServices.push(...(await this.didCommDocumentService.resolveServicesFromDid(service)))
} else {
// Out of band inline service contains keys encoded as did:key references
didCommServices.push({
Expand Down
45 changes: 37 additions & 8 deletions packages/core/src/agent/__tests__/MessageSender.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import type { ConnectionRecord } from '../../modules/connections'
import type { ResolvedDidCommService } from '../../modules/didcomm'
import type { DidDocumentService } from '../../modules/dids'
import type { MessageRepository } from '../../storage/MessageRepository'
import type { OutboundTransport } from '../../transport'
import type { OutboundMessage, EncryptedMessage } from '../../types'
import type { ResolvedDidCommService } from '../MessageSender'

import { TestMessage } from '../../../tests/TestMessage'
import { getAgentConfig, getMockConnection, mockFunction } from '../../../tests/helpers'
import testLogger from '../../../tests/logger'
import { KeyType } from '../../crypto'
import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator'
import { Key, DidDocument, VerificationMethod } from '../../modules/dids'
import { DidCommDocumentService } from '../../modules/didcomm'
import { DidResolverService, Key, DidDocument, VerificationMethod } from '../../modules/dids'
import { DidCommV1Service } from '../../modules/dids/domain/service/DidCommV1Service'
import { DidResolverService } from '../../modules/dids/services/DidResolverService'
import { verkeyToInstanceOfKey } from '../../modules/dids/helpers'
import { OutOfBandRepository } from '../../modules/oob'
import { InMemoryMessageRepository } from '../../storage/InMemoryMessageRepository'
import { EnvelopeService as EnvelopeServiceImpl } from '../EnvelopeService'
import { MessageSender } from '../MessageSender'
Expand All @@ -24,11 +26,15 @@ import { DummyTransportSession } from './stubs'
jest.mock('../TransportService')
jest.mock('../EnvelopeService')
jest.mock('../../modules/dids/services/DidResolverService')
jest.mock('../../modules/didcomm/services/DidCommDocumentService')
jest.mock('../../modules/oob/repository/OutOfBandRepository')

const logger = testLogger

const TransportServiceMock = TransportService as jest.MockedClass<typeof TransportService>
const DidResolverServiceMock = DidResolverService as jest.Mock<DidResolverService>
const DidCommDocumentServiceMock = DidCommDocumentService as jest.Mock<DidCommDocumentService>
const OutOfBandRepositoryMock = OutOfBandRepository as jest.Mock<OutOfBandRepository>

class DummyHttpOutboundTransport implements OutboundTransport {
public start(): Promise<void> {
Expand Down Expand Up @@ -76,7 +82,10 @@ describe('MessageSender', () => {
const envelopeServicePackMessageMock = mockFunction(enveloperService.packMessage)

const didResolverService = new DidResolverServiceMock()
const didCommDocumentService = new DidCommDocumentServiceMock()
const outOfBandRepository = new OutOfBandRepositoryMock()
const didResolverServiceResolveMock = mockFunction(didResolverService.resolveDidDocument)
const didResolverServiceResolveDidServicesMock = mockFunction(didCommDocumentService.resolveServicesFromDid)

const inboundMessage = new TestMessage()
inboundMessage.setReturnRouting(ReturnRouteTypes.all)
Expand Down Expand Up @@ -130,7 +139,9 @@ describe('MessageSender', () => {
transportService,
messageRepository,
logger,
didResolverService
didResolverService,
didCommDocumentService,
outOfBandRepository
)
connection = getMockConnection({
id: 'test-123',
Expand All @@ -147,6 +158,10 @@ describe('MessageSender', () => {
service: [firstDidCommService, secondDidCommService],
})
didResolverServiceResolveMock.mockResolvedValue(didDocumentInstance)
didResolverServiceResolveDidServicesMock.mockResolvedValue([
getMockResolvedDidService(firstDidCommService),
getMockResolvedDidService(secondDidCommService),
])
})

afterEach(() => {
Expand All @@ -161,6 +176,7 @@ describe('MessageSender', () => {
messageSender.registerOutboundTransport(outboundTransport)

didResolverServiceResolveMock.mockResolvedValue(getMockDidDocument({ service: [] }))
didResolverServiceResolveDidServicesMock.mockResolvedValue([])

await expect(messageSender.sendMessage(outboundMessage)).rejects.toThrow(
`Message is undeliverable to connection test-123 (Test 123)`
Expand All @@ -186,14 +202,14 @@ describe('MessageSender', () => {
expect(sendMessageSpy).toHaveBeenCalledTimes(1)
})

test("resolves the did document using the did resolver if connection.theirDid starts with 'did:'", async () => {
test("resolves the did service using the did resolver if connection.theirDid starts with 'did:'", async () => {
messageSender.registerOutboundTransport(outboundTransport)

const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage')

await messageSender.sendMessage(outboundMessage)

expect(didResolverServiceResolveMock).toHaveBeenCalledWith(connection.theirDid)
expect(didResolverServiceResolveDidServicesMock).toHaveBeenCalledWith(connection.theirDid)
expect(sendMessageSpy).toHaveBeenCalledWith({
connectionId: 'test-123',
payload: encryptedMessage,
Expand Down Expand Up @@ -326,7 +342,9 @@ describe('MessageSender', () => {
transportService,
new InMemoryMessageRepository(getAgentConfig('MessageSenderTest')),
logger,
didResolverService
didResolverService,
didCommDocumentService,
outOfBandRepository
)

envelopeServicePackMessageMock.mockReturnValue(Promise.resolve(encryptedMessage))
Expand Down Expand Up @@ -406,7 +424,9 @@ describe('MessageSender', () => {
transportService,
messageRepository,
logger,
didResolverService
didResolverService,
didCommDocumentService,
outOfBandRepository
)
connection = getMockConnection()

Expand Down Expand Up @@ -454,3 +474,12 @@ function getMockDidDocument({ service }: { service: DidDocumentService[] }) {
],
})
}

function getMockResolvedDidService(service: DidDocumentService): ResolvedDidCommService {
return {
id: service.id,
serviceEndpoint: service.serviceEndpoint,
recipientKeys: [verkeyToInstanceOfKey('EoGusetSxDJktp493VCyh981nUnzMamTRjvBaHZAy68d')],
routingKeys: [],
}
}
2 changes: 1 addition & 1 deletion packages/core/src/agent/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { ConnectionRecord } from '../modules/connections'
import type { ResolvedDidCommService } from '../modules/didcomm'
import type { Key } from '../modules/dids/domain/Key'
import type { OutOfBandRecord } from '../modules/oob/repository'
import type { OutboundMessage, OutboundServiceMessage } from '../types'
import type { AgentMessage } from './AgentMessage'
import type { ResolvedDidCommService } from './MessageSender'

export function createOutboundMessage<T extends AgentMessage = AgentMessage>(
connection: ConnectionRecord,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/decorators/service/ServiceDecorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ResolvedDidCommService } from '../../agent/MessageSender'
import type { ResolvedDidCommService } from '../../modules/didcomm'

import { IsArray, IsOptional, IsString } from 'class-validator'

Expand Down
12 changes: 5 additions & 7 deletions packages/core/src/modules/connections/DidExchangeProtocol.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { ResolvedDidCommService } from '../../agent/MessageSender'
import type { InboundMessageContext } from '../../agent/models/InboundMessageContext'
import type { Logger } from '../../logger'
import type { ParsedMessageType } from '../../utils/messageType'
import type { OutOfBandDidCommService } from '../oob/domain/OutOfBandDidCommService'
import type { ResolvedDidCommService } from '../didcomm'
import type { OutOfBandRecord } from '../oob/repository'
import type { ConnectionRecord } from './repository'
import type { Routing } from './services/ConnectionService'
Expand Down Expand Up @@ -221,10 +220,7 @@ export class DidExchangeProtocol {
if (routing) {
services = this.routingToServices(routing)
} else if (outOfBandRecord) {
const inlineServices = outOfBandRecord.outOfBandInvitation.services.filter(
(service) => typeof service !== 'string'
) as OutOfBandDidCommService[]

const inlineServices = outOfBandRecord.outOfBandInvitation.getInlineServices()
services = inlineServices.map((service) => ({
id: service.id,
serviceEndpoint: service.serviceEndpoint,
Expand Down Expand Up @@ -300,7 +296,9 @@ export class DidExchangeProtocol {

const didDocument = await this.extractDidDocument(
message,
outOfBandRecord.outOfBandInvitation.getRecipientKeys().map((key) => key.publicKeyBase58)
outOfBandRecord
.getTags()
.recipientKeyFingerprints.map((fingerprint) => Key.fromFingerprint(fingerprint).publicKeyBase58)
)
const didRecord = new DidRecord({
id: message.did,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export class ConnectionResponseHandler implements Handler {
}

messageContext.connection = connectionRecord
// The presence of outOfBandRecord is not mandatory when the old connection invitation is used
const connection = await this.connectionService.processResponse(messageContext, outOfBandRecord)

// TODO: should we only send ping message in case of autoAcceptConnection or always?
Expand Down
34 changes: 15 additions & 19 deletions packages/core/src/modules/connections/services/ConnectionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ export class ConnectionService {
didDoc,
})

const { label, imageUrl } = config
const connectionRequest = new ConnectionRequestMessage({
label: label ?? this.config.label,
did: didDoc.id,
didDoc,
imageUrl: imageUrl ?? this.config.connectionImageUrl,
})

connectionRequest.setThread({
threadId: connectionRequest.id,
parentThreadId: outOfBandInvitation.id,
})

const connectionRecord = await this.createConnection({
protocol: HandshakeProtocol.Connections,
role: DidExchangeRole.Requester,
Expand All @@ -121,22 +134,9 @@ export class ConnectionService {
outOfBandId: outOfBandRecord.id,
invitationDid,
imageUrl: outOfBandInvitation.imageUrl,
threadId: connectionRequest.id,
})

const { label, imageUrl, autoAcceptConnection } = config

const connectionRequest = new ConnectionRequestMessage({
label: label ?? this.config.label,
did: didDoc.id,
didDoc,
imageUrl: imageUrl ?? this.config.connectionImageUrl,
})

if (autoAcceptConnection !== undefined || autoAcceptConnection !== null) {
connectionRecord.autoAcceptConnection = config?.autoAcceptConnection
}

connectionRecord.threadId = connectionRequest.id
await this.updateState(connectionRecord, DidExchangeState.RequestSent)

return {
Expand Down Expand Up @@ -204,11 +204,7 @@ export class ConnectionService {

const didDoc = routing
? this.createDidDoc(routing)
: this.createDidDocFromOutOfBandDidCommServices(
outOfBandRecord.outOfBandInvitation.services.filter(
(s): s is OutOfBandDidCommService => typeof s !== 'string'
)
)
: this.createDidDocFromOutOfBandDidCommServices(outOfBandRecord.outOfBandInvitation.getInlineServices())

const { did: peerDid } = await this.createDid({
role: DidDocumentRole.Created,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/modules/didcomm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './types'
export * from './services'
Loading

0 comments on commit c99f3c9

Please sign in to comment.