Skip to content

Commit

Permalink
feat: add out-of-band and did exchange (#717)
Browse files Browse the repository at this point in the history
Signed-off-by: Jakub Koci <[email protected]>
Co-authored-by: Timo Glastra <[email protected]>

BREAKING CHANGE: the connections module has been extended with an out of band module and support for the DID Exchange protocol. Some methods have been moved to the out of band module, see [Migrating from AFJ 0.1.0 to 0.2.x](https://github.com/hyperledger/aries-framework-javascript/blob/main/docs/migration/0.1-to-0.2.md) for detailed migration instructions.
  • Loading branch information
jakubkoci authored May 13, 2022
1 parent cbdff28 commit 16c6d60
Show file tree
Hide file tree
Showing 154 changed files with 6,944 additions and 1,934 deletions.
17 changes: 13 additions & 4 deletions demo/src/Alice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,20 @@ export class Alice extends BaseAgent {
}

private async printConnectionInvite() {
const invite = await this.agent.connections.createConnection()
this.connectionRecordFaberId = invite.connectionRecord.id
const outOfBand = await this.agent.oob.createInvitation()
// FIXME: this won't work as oob doesn't create a connection immediately
const [connectionRecord] = await this.agent.connections.findAllByOutOfBandId(outOfBand.id)
if (!connectionRecord) {
throw new Error(redText(Output.NoConnectionRecordFromOutOfBand))
}
this.connectionRecordFaberId = connectionRecord.id

console.log(Output.ConnectionLink, invite.invitation.toUrl({ domain: `http://localhost:${this.port}` }), '\n')
return invite.connectionRecord
console.log(
Output.ConnectionLink,
outOfBand.outOfBandInvitation.toUrl({ domain: `http://localhost:${this.port}` }),
'\n'
)
return connectionRecord
}

private async waitForConnection() {
Expand Down
6 changes: 5 additions & 1 deletion demo/src/Faber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ export class Faber extends BaseAgent {
}

private async receiveConnectionRequest(invitationUrl: string) {
return await this.agent.connections.receiveInvitationFromUrl(invitationUrl)
const { connectionRecord } = await this.agent.oob.receiveInvitationFromUrl(invitationUrl)
if (!connectionRecord) {
throw new Error(redText(Output.NoConnectionRecordFromOutOfBand))
}
return connectionRecord
}

private async waitForConnection(connectionRecord: ConnectionRecord) {
Expand Down
1 change: 1 addition & 0 deletions demo/src/OutputClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum Color {
}

export enum Output {
NoConnectionRecordFromOutOfBand = `\nNo connectionRecord has been created from invitation\n`,
ConnectionEstablished = `\nConnection established!`,
MissingConnectionRecord = `\nNo connectionRecord ID has been set yet\n`,
ConnectionLink = `\nRun 'Receive connection invitation' in Faber and paste this invitation link:\n\n`,
Expand Down
40 changes: 37 additions & 3 deletions packages/core/src/agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { CredentialsModule } from '../modules/credentials/CredentialsModule'
import { DidsModule } from '../modules/dids/DidsModule'
import { DiscoverFeaturesModule } from '../modules/discover-features'
import { LedgerModule } from '../modules/ledger/LedgerModule'
import { OutOfBandModule } from '../modules/oob/OutOfBandModule'
import { ProofsModule } from '../modules/proofs/ProofsModule'
import { MediatorModule } from '../modules/routing/MediatorModule'
import { RecipientModule } from '../modules/routing/RecipientModule'
Expand Down Expand Up @@ -61,6 +62,7 @@ export class Agent {
public readonly discovery: DiscoverFeaturesModule
public readonly dids: DidsModule
public readonly wallet: WalletModule
public readonly oob!: OutOfBandModule

public constructor(
initialConfig: InitConfig,
Expand Down Expand Up @@ -123,13 +125,14 @@ export class Agent {
this.discovery = this.container.resolve(DiscoverFeaturesModule)
this.dids = this.container.resolve(DidsModule)
this.wallet = this.container.resolve(WalletModule)
this.oob = this.container.resolve(OutOfBandModule)

// Listen for new messages (either from transports or somewhere else in the framework / extensions)
this.messageSubscription = this.eventEmitter
.observable<AgentMessageReceivedEvent>(AgentEventTypes.AgentMessageReceived)
.pipe(
takeUntil(this.agentConfig.stop$),
concatMap((e) => this.messageReceiver.receiveMessage(e.payload.message))
concatMap((e) => this.messageReceiver.receiveMessage(e.payload.message, { connection: e.payload.connection }))
)
.subscribe()
}
Expand Down Expand Up @@ -224,7 +227,9 @@ export class Agent {
// Also requests mediation ans sets as default mediator
// Because this requires the connections module, we do this in the agent constructor
if (mediatorConnectionsInvite) {
await this.mediationRecipient.provision(mediatorConnectionsInvite)
this.logger.debug('Provision mediation with invitation', { mediatorConnectionsInvite })
const mediatonConnection = await this.getMediationConnection(mediatorConnectionsInvite)
await this.mediationRecipient.provision(mediatonConnection)
}

await this.mediationRecipient.initialize()
Expand Down Expand Up @@ -254,7 +259,7 @@ export class Agent {
}

public async receiveMessage(inboundMessage: unknown, session?: TransportSession) {
await this.messageReceiver.receiveMessage(inboundMessage, session)
return await this.messageReceiver.receiveMessage(inboundMessage, { session })
}

public get injectionContainer() {
Expand All @@ -264,4 +269,33 @@ export class Agent {
public get config() {
return this.agentConfig
}

private async getMediationConnection(mediatorInvitationUrl: string) {
const outOfBandInvitation = await this.oob.parseInvitation(mediatorInvitationUrl)
const outOfBandRecord = await this.oob.findByInvitationId(outOfBandInvitation.id)
const [connection] = outOfBandRecord ? await this.connections.findAllByOutOfBandId(outOfBandRecord.id) : []

if (!connection) {
this.logger.debug('Mediation connection does not exist, creating connection')
// We don't want to use the current default mediator when connecting to another mediator
const routing = await this.mediationRecipient.getRouting({ useDefaultMediator: false })

this.logger.debug('Routing created', routing)
const { connectionRecord: newConnection } = await this.oob.receiveInvitation(outOfBandInvitation, {
routing,
})
this.logger.debug(`Mediation invitation processed`, { outOfBandInvitation })

if (!newConnection) {
throw new AriesFrameworkError('No connection record to provision mediation.')
}

return this.connections.returnWhenIsConnected(newConnection.id)
}

if (!connection.isReady) {
return this.connections.returnWhenIsConnected(connection.id)
}
return connection
}
}
4 changes: 2 additions & 2 deletions packages/core/src/agent/Dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ class Dispatcher {
this.logger.error(`Error handling message with type ${message.type}`, {
message: message.toJSON(),
error,
senderVerkey: messageContext.senderVerkey,
recipientVerkey: messageContext.recipientVerkey,
senderKey: messageContext.senderKey?.fingerprint,
recipientKey: messageContext.recipientKey?.fingerprint,
connectionId: messageContext.connection?.id,
})

Expand Down
42 changes: 28 additions & 14 deletions packages/core/src/agent/EnvelopeService.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import type { Logger } from '../logger'
import type { DecryptedMessageContext, EncryptedMessage } from '../types'
import type { EncryptedMessage, PlaintextMessage } from '../types'
import type { AgentMessage } from './AgentMessage'

import { inject, scoped, Lifecycle } from 'tsyringe'

import { InjectionSymbols } from '../constants'
import { KeyType } from '../crypto'
import { Key } from '../modules/dids'
import { ForwardMessage } from '../modules/routing/messages'
import { Wallet } from '../wallet/Wallet'

import { AgentConfig } from './AgentConfig'

export interface EnvelopeKeys {
recipientKeys: string[]
routingKeys: string[]
senderKey: string | null
recipientKeys: Key[]
routingKeys: Key[]
senderKey: Key | null
}

@scoped(Lifecycle.ContainerScoped)
class EnvelopeService {
export class EnvelopeService {
private wallet: Wallet
private logger: Logger
private config: AgentConfig
Expand All @@ -29,38 +31,50 @@ class EnvelopeService {
}

public async packMessage(payload: AgentMessage, keys: EnvelopeKeys): Promise<EncryptedMessage> {
const { routingKeys, senderKey } = keys
let recipientKeys = keys.recipientKeys
const { recipientKeys, routingKeys, senderKey } = keys
let recipientKeysBase58 = recipientKeys.map((key) => key.publicKeyBase58)
const routingKeysBase58 = routingKeys.map((key) => key.publicKeyBase58)
const senderKeyBase58 = senderKey && senderKey.publicKeyBase58

// pass whether we want to use legacy did sov prefix
const message = payload.toJSON({ useLegacyDidSovPrefix: this.config.useLegacyDidSovPrefix })

this.logger.debug(`Pack outbound message ${message['@type']}`)

let encryptedMessage = await this.wallet.pack(message, recipientKeys, senderKey ?? undefined)
let encryptedMessage = await this.wallet.pack(message, recipientKeysBase58, senderKeyBase58 ?? undefined)

// If the message has routing keys (mediator) pack for each mediator
for (const routingKey of routingKeys) {
for (const routingKeyBase58 of routingKeysBase58) {
const forwardMessage = new ForwardMessage({
// Forward to first recipient key
to: recipientKeys[0],
to: recipientKeysBase58[0],
message: encryptedMessage,
})
recipientKeys = [routingKey]
recipientKeysBase58 = [routingKeyBase58]
this.logger.debug('Forward message created', forwardMessage)

const forwardJson = forwardMessage.toJSON({ useLegacyDidSovPrefix: this.config.useLegacyDidSovPrefix })

// Forward messages are anon packed
encryptedMessage = await this.wallet.pack(forwardJson, [routingKey], undefined)
encryptedMessage = await this.wallet.pack(forwardJson, [routingKeyBase58], undefined)
}

return encryptedMessage
}

public async unpackMessage(encryptedMessage: EncryptedMessage): Promise<DecryptedMessageContext> {
return this.wallet.unpack(encryptedMessage)
const decryptedMessage = await this.wallet.unpack(encryptedMessage)
const { recipientKey, senderKey, plaintextMessage } = decryptedMessage
return {
recipientKey: recipientKey ? Key.fromPublicKeyBase58(recipientKey, KeyType.Ed25519) : undefined,
senderKey: senderKey ? Key.fromPublicKeyBase58(senderKey, KeyType.Ed25519) : undefined,
plaintextMessage,
}
}
}

export { EnvelopeService }
export interface DecryptedMessageContext {
plaintextMessage: PlaintextMessage
senderKey?: Key
recipientKey?: Key
}
1 change: 1 addition & 0 deletions packages/core/src/agent/Events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface AgentMessageReceivedEvent extends BaseEvent {
type: typeof AgentEventTypes.AgentMessageReceived
payload: {
message: unknown
connection?: ConnectionRecord
}
}

Expand Down
Loading

0 comments on commit 16c6d60

Please sign in to comment.