Skip to content

Commit

Permalink
feat(dids): add did registrar (#953)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <timo@animo.id>
  • Loading branch information
TimoGlastra committed Aug 26, 2022
1 parent 80c3740 commit 93f3c93
Show file tree
Hide file tree
Showing 56 changed files with 2,892 additions and 289 deletions.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@stablelib/ed25519": "^1.0.2",
"@stablelib/random": "^1.0.1",
"@stablelib/sha256": "^1.0.1",
"@types/indy-sdk": "^1.16.19",
"@types/indy-sdk": "^1.16.21",
"@types/node-fetch": "^2.5.10",
"@types/ws": "^7.4.6",
"abort-controller": "^3.0.0",
Expand Down
55 changes: 30 additions & 25 deletions packages/core/src/modules/connections/DidExchangeProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AgentContext } from '../../agent'
import type { ResolvedDidCommService } from '../../agent/MessageSender'
import type { InboundMessageContext } from '../../agent/models/InboundMessageContext'
import type { ParsedMessageType } from '../../utils/messageType'
import type { PeerDidCreateOptions } from '../dids'
import type { OutOfBandDidCommService } from '../oob/domain/OutOfBandDidCommService'
import type { OutOfBandRecord } from '../oob/repository'
import type { ConnectionRecord } from './repository'
Expand All @@ -16,14 +17,17 @@ import { Logger } from '../../logger'
import { inject, injectable } from '../../plugins'
import { JsonEncoder } from '../../utils/JsonEncoder'
import { JsonTransformer } from '../../utils/JsonTransformer'
import { DidDocument } from '../dids'
import { DidDocumentRole } from '../dids/domain/DidDocumentRole'
import { createDidDocumentFromServices } from '../dids/domain/createPeerDidFromServices'
import {
DidDocument,
DidRegistrarService,
DidDocumentRole,
createPeerDidDocumentFromServices,
DidKey,
getNumAlgoFromPeerDid,
PeerDidNumAlgo,
} from '../dids'
import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type'
import { didKeyToInstanceOfKey } from '../dids/helpers'
import { DidKey } from '../dids/methods/key/DidKey'
import { getNumAlgoFromPeerDid, PeerDidNumAlgo } from '../dids/methods/peer/didPeer'
import { didDocumentJsonToNumAlgo1Did } from '../dids/methods/peer/peerDidNumAlgo1'
import { DidRecord, DidRepository } from '../dids/repository'
import { OutOfBandRole } from '../oob/domain/OutOfBandRole'
import { OutOfBandState } from '../oob/domain/OutOfBandState'
Expand All @@ -48,17 +52,20 @@ interface DidExchangeRequestParams {
@injectable()
export class DidExchangeProtocol {
private connectionService: ConnectionService
private didRegistrarService: DidRegistrarService
private jwsService: JwsService
private didRepository: DidRepository
private logger: Logger

public constructor(
connectionService: ConnectionService,
didRegistrarService: DidRegistrarService,
didRepository: DidRepository,
jwsService: JwsService,
@inject(InjectionSymbols.Logger) logger: Logger
) {
this.connectionService = connectionService
this.didRegistrarService = didRegistrarService
this.didRepository = didRepository
this.jwsService = jwsService
this.logger = logger
Expand Down Expand Up @@ -165,6 +172,8 @@ export class DidExchangeProtocol {
)
}

// TODO: Move this into the didcomm module, and add a method called store received did document.
// This can be called from both the did exchange and the connection protocol.
const didDocument = await this.extractDidDocument(messageContext.agentContext, message)
const didRecord = new DidRecord({
id: message.did,
Expand Down Expand Up @@ -406,32 +415,28 @@ export class DidExchangeProtocol {
}

private async createPeerDidDoc(agentContext: AgentContext, services: ResolvedDidCommService[]) {
const didDocument = createDidDocumentFromServices(services)
// Create did document without the id property
const didDocument = createPeerDidDocumentFromServices(services)

const peerDid = didDocumentJsonToNumAlgo1Did(didDocument.toJSON())
didDocument.id = peerDid

const didRecord = new DidRecord({
id: peerDid,
role: DidDocumentRole.Created,
// Register did:peer document. This will generate the id property and save it to a did record
const result = await this.didRegistrarService.create<PeerDidCreateOptions>(agentContext, {
method: 'peer',
didDocument,
tags: {
// We need to save the recipientKeys, so we can find the associated did
// of a key when we receive a message from another connection.
recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint),
options: {
numAlgo: PeerDidNumAlgo.GenesisDoc,
},
})

this.logger.debug('Saving DID record', {
id: didRecord.id,
role: didRecord.role,
tags: didRecord.getTags(),
didDocument: 'omitted...',
if (result.didState?.state !== 'finished') {
throw new AriesFrameworkError(`Did document creation failed: ${JSON.stringify(result.didState)}`)
}

this.logger.debug(`Did document with did ${result.didState.did} created.`, {
did: result.didState.did,
didDocument: result.didState.didDocument,
})

await this.didRepository.save(agentContext, didRecord)
this.logger.debug('Did record created.', didRecord)
return didDocument
return result.didState.didDocument
}

private async createSignedAttachment(agentContext: AgentContext, didDoc: DidDocument, verkeys: string[]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AgentContext } from '../../../agent'
import type { Wallet } from '../../../wallet/Wallet'
import type { DidDocument } from '../../dids'
import type { Routing } from '../services/ConnectionService'

import { Subject } from 'rxjs'
Expand All @@ -22,9 +23,11 @@ import { uuid } from '../../../utils/uuid'
import { IndyWallet } from '../../../wallet/IndyWallet'
import { AckMessage, AckStatus } from '../../common'
import { DidKey, IndyAgentService } from '../../dids'
import { DidDocumentRole } from '../../dids/domain/DidDocumentRole'
import { DidCommV1Service } from '../../dids/domain/service/DidCommV1Service'
import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1'
import { DidRepository } from '../../dids/repository'
import { DidRecord, DidRepository } from '../../dids/repository'
import { DidRegistrarService } from '../../dids/services/DidRegistrarService'
import { OutOfBandRole } from '../../oob/domain/OutOfBandRole'
import { OutOfBandState } from '../../oob/domain/OutOfBandState'
import { ConnectionRequestMessage, ConnectionResponseMessage, TrustPingMessage } from '../messages'
Expand All @@ -43,9 +46,22 @@ import { ConnectionService } from '../services/ConnectionService'
import { convertToNewDidDocument } from '../services/helpers'

jest.mock('../repository/ConnectionRepository')
jest.mock('../../dids/services/DidRegistrarService')
jest.mock('../../dids/repository/DidRepository')
const ConnectionRepositoryMock = ConnectionRepository as jest.Mock<ConnectionRepository>
const DidRepositoryMock = DidRepository as jest.Mock<DidRepository>
const DidRegistrarServiceMock = DidRegistrarService as jest.Mock<DidRegistrarService>

const didRegistrarService = new DidRegistrarServiceMock()
mockFunction(didRegistrarService.create).mockResolvedValue({
didDocumentMetadata: {},
didRegistrationMetadata: {},
didState: {
state: 'finished',
did: 'did:peer:123',
didDocument: {} as DidDocument,
},
})

const connectionImageUrl = 'https://example.com/image.png'

Expand Down Expand Up @@ -78,13 +94,26 @@ describe('ConnectionService', () => {
eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject())
connectionRepository = new ConnectionRepositoryMock()
didRepository = new DidRepositoryMock()
connectionService = new ConnectionService(agentConfig.logger, connectionRepository, didRepository, eventEmitter)
connectionService = new ConnectionService(
agentConfig.logger,
connectionRepository,
didRepository,
didRegistrarService,
eventEmitter
)
myRouting = {
recipientKey: Key.fromFingerprint('z6MkwFkSP4uv5PhhKJCGehtjuZedkotC7VF64xtMsxuM8R3W'),
endpoints: agentConfig.endpoints ?? [],
routingKeys: [],
mediatorId: 'fakeMediatorId',
}

mockFunction(didRepository.getById).mockResolvedValue(
new DidRecord({
id: 'did:peer:123',
role: DidDocumentRole.Created,
})
)
})

describe('createRequest', () => {
Expand Down
78 changes: 54 additions & 24 deletions packages/core/src/modules/connections/services/ConnectionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AgentContext } from '../../../agent'
import type { AgentMessage } from '../../../agent/AgentMessage'
import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext'
import type { AckMessage } from '../../common'
import type { PeerDidCreateOptions } from '../../dids/methods/peer/PeerDidRegistrar'
import type { OutOfBandDidCommService } from '../../oob/domain/OutOfBandDidCommService'
import type { OutOfBandRecord } from '../../oob/repository'
import type { ConnectionStateChangedEvent } from '../ConnectionEvents'
Expand All @@ -21,9 +22,10 @@ import { Logger } from '../../../logger'
import { inject, injectable } from '../../../plugins'
import { JsonTransformer } from '../../../utils/JsonTransformer'
import { indyDidFromPublicKeyBase58 } from '../../../utils/did'
import { DidKey, IndyAgentService } from '../../dids'
import { DidKey, DidRegistrarService, IndyAgentService } from '../../dids'
import { DidDocumentRole } from '../../dids/domain/DidDocumentRole'
import { didKeyToVerkey } from '../../dids/helpers'
import { PeerDidNumAlgo } from '../../dids/methods/peer/didPeer'
import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1'
import { DidRecord, DidRepository } from '../../dids/repository'
import { DidRecordMetadataKeys } from '../../dids/repository/didRecordMetadataTypes'
Expand Down Expand Up @@ -59,17 +61,20 @@ export interface ConnectionRequestParams {
export class ConnectionService {
private connectionRepository: ConnectionRepository
private didRepository: DidRepository
private didRegistrarService: DidRegistrarService
private eventEmitter: EventEmitter
private logger: Logger

public constructor(
@inject(InjectionSymbols.Logger) logger: Logger,
connectionRepository: ConnectionRepository,
didRepository: DidRepository,
didRegistrarService: DidRegistrarService,
eventEmitter: EventEmitter
) {
this.connectionRepository = connectionRepository
this.didRepository = didRepository
this.didRegistrarService = didRegistrarService
this.eventEmitter = eventEmitter
this.logger = logger
}
Expand Down Expand Up @@ -101,18 +106,15 @@ export class ConnectionService {
// We take just the first one for now.
const [invitationDid] = outOfBandInvitation.invitationDids

const { did: peerDid } = await this.createDid(agentContext, {
role: DidDocumentRole.Created,
didDoc,
})
const didDocument = await this.registerCreatedPeerDidDocument(agentContext, didDoc)

const connectionRecord = await this.createConnection(agentContext, {
protocol: HandshakeProtocol.Connections,
role: DidExchangeRole.Requester,
state: DidExchangeState.InvitationReceived,
theirLabel: outOfBandInvitation.label,
alias: config?.alias,
did: peerDid,
did: didDocument.id,
mediatorId,
autoAcceptConnection: config?.autoAcceptConnection,
outOfBandId: outOfBandRecord.id,
Expand Down Expand Up @@ -161,10 +163,7 @@ export class ConnectionService {
})
}

const { did: peerDid } = await this.createDid(messageContext.agentContext, {
role: DidDocumentRole.Received,
didDoc: message.connection.didDoc,
})
const didDocument = await this.storeReceivedPeerDidDocument(messageContext.agentContext, message.connection.didDoc)

const connectionRecord = await this.createConnection(messageContext.agentContext, {
protocol: HandshakeProtocol.Connections,
Expand All @@ -173,7 +172,7 @@ export class ConnectionService {
theirLabel: message.label,
imageUrl: message.imageUrl,
outOfBandId: outOfBandRecord.id,
theirDid: peerDid,
theirDid: didDocument.id,
threadId: message.threadId,
mediatorId: outOfBandRecord.mediatorId,
autoAcceptConnection: outOfBandRecord.autoAcceptConnection,
Expand Down Expand Up @@ -210,10 +209,7 @@ export class ConnectionService {
)
)

const { did: peerDid } = await this.createDid(agentContext, {
role: DidDocumentRole.Created,
didDoc,
})
const didDocument = await this.registerCreatedPeerDidDocument(agentContext, didDoc)

const connection = new Connection({
did: didDoc.id,
Expand All @@ -233,7 +229,7 @@ export class ConnectionService {
connectionSig: await signData(connectionJson, agentContext.wallet, signingKey),
})

connectionRecord.did = peerDid
connectionRecord.did = didDocument.id
await this.updateState(agentContext, connectionRecord, DidExchangeState.ResponseSent)

this.logger.debug(`Create message ${ConnectionResponseMessage.type.messageTypeUri} end`, {
Expand Down Expand Up @@ -309,12 +305,9 @@ export class ConnectionService {
throw new AriesFrameworkError('DID Document is missing.')
}

const { did: peerDid } = await this.createDid(messageContext.agentContext, {
role: DidDocumentRole.Received,
didDoc: connection.didDoc,
})
const didDocument = await this.storeReceivedPeerDidDocument(messageContext.agentContext, connection.didDoc)

connectionRecord.theirDid = peerDid
connectionRecord.theirDid = didDocument.id
connectionRecord.threadId = message.threadId

await this.updateState(messageContext.agentContext, connectionRecord, DidExchangeState.ResponseReceived)
Expand Down Expand Up @@ -632,15 +625,52 @@ export class ConnectionService {
return connectionRecord
}

private async createDid(agentContext: AgentContext, { role, didDoc }: { role: DidDocumentRole; didDoc: DidDoc }) {
private async registerCreatedPeerDidDocument(agentContext: AgentContext, didDoc: DidDoc) {
// Convert the legacy did doc to a new did document
const didDocument = convertToNewDidDocument(didDoc)

// Register did:peer document. This will generate the id property and save it to a did record
const result = await this.didRegistrarService.create<PeerDidCreateOptions>(agentContext, {
method: 'peer',
didDocument,
options: {
numAlgo: PeerDidNumAlgo.GenesisDoc,
},
})

if (result.didState?.state !== 'finished') {
throw new AriesFrameworkError(`Did document creation failed: ${JSON.stringify(result.didState)}`)
}

this.logger.debug(`Did document with did ${result.didState.did} created.`, {
did: result.didState.did,
didDocument: result.didState.didDocument,
})

const didRecord = await this.didRepository.getById(agentContext, result.didState.did)

// Store the unqualified did with the legacy did document in the metadata
// Can be removed at a later stage if we know for sure we don't need it anymore
didRecord.metadata.set(DidRecordMetadataKeys.LegacyDid, {
unqualifiedDid: didDoc.id,
didDocumentString: JsonTransformer.serialize(didDoc),
})

await this.didRepository.update(agentContext, didRecord)
return result.didState.didDocument
}

private async storeReceivedPeerDidDocument(agentContext: AgentContext, didDoc: DidDoc) {
// Convert the legacy did doc to a new did document
const didDocument = convertToNewDidDocument(didDoc)

// TODO: Move this into the didcomm module, and add a method called store received did document.
// This can be called from both the did exchange and the connection protocol.
const peerDid = didDocumentJsonToNumAlgo1Did(didDocument.toJSON())
didDocument.id = peerDid
const didRecord = new DidRecord({
id: peerDid,
role,
role: DidDocumentRole.Received,
didDocument,
tags: {
// We need to save the recipientKeys, so we can find the associated did
Expand All @@ -665,7 +695,7 @@ export class ConnectionService {

await this.didRepository.save(agentContext, didRecord)
this.logger.debug('Did record created.', didRecord)
return { did: peerDid, didDocument }
return didDocument
}

private createDidDoc(routing: Routing) {
Expand Down
Loading

0 comments on commit 93f3c93

Please sign in to comment.