Skip to content

Commit

Permalink
feat: use did:key flag (#1029)
Browse files Browse the repository at this point in the history
Signed-off-by: Ariel Gentile <gentilester@gmail.com>
  • Loading branch information
genaris committed Sep 20, 2022
1 parent c789081 commit 8efade5
Show file tree
Hide file tree
Showing 12 changed files with 318 additions and 68 deletions.
10 changes: 10 additions & 0 deletions packages/core/src/agent/AgentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ export class AgentConfig {
return this.initConfig.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY
}

/**
* Encode keys in did:key format instead of 'naked' keys, as stated in Aries RFC 0360.
*
* This setting will not be taken into account if the other party has previously used naked keys
* in a given protocol (i.e. it does not support Aries RFC 0360).
*/
public get useDidKeyInProtocols() {
return this.initConfig.useDidKeyInProtocols ?? false
}

public get endpoints(): [string, ...string[]] {
// if endpoints is not set, return queue endpoint
// https://github.com/hyperledger/aries-rfcs/issues/405#issuecomment-582612875
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum ConnectionMetadataKeys {
UseDidKeysForProtocol = '_internal/useDidKeysForProtocol',
}

export type ConnectionMetadata = {
[ConnectionMetadataKeys.UseDidKeysForProtocol]: {
[protocolUri: string]: boolean
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { TagsBase } from '../../../storage/BaseRecord'
import type { HandshakeProtocol } from '../models'
import type { ConnectionMetadata } from './ConnectionMetadataTypes'

import { AriesFrameworkError } from '../../../error'
import { BaseRecord } from '../../../storage/BaseRecord'
Expand Down Expand Up @@ -39,7 +40,7 @@ export type DefaultConnectionTags = {
}

export class ConnectionRecord
extends BaseRecord<DefaultConnectionTags, CustomConnectionTags>
extends BaseRecord<DefaultConnectionTags, CustomConnectionTags, ConnectionMetadata>
implements ConnectionRecordProps
{
public state!: DidExchangeState
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/modules/dids/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import { KeyType } from '../../crypto'
import { Key } from './domain/Key'
import { DidKey } from './methods/key'

export function isDidKey(key: string) {
return key.startsWith('did:key')
}

export function didKeyToVerkey(key: string) {
if (key.startsWith('did:key')) {
if (isDidKey(key)) {
const publicKeyBase58 = DidKey.fromDid(key).key.publicKeyBase58
return publicKeyBase58
}
return key
}

export function verkeyToDidKey(key: string) {
if (key.startsWith('did:key')) {
if (isDidKey(key)) {
return key
}
const publicKeyBase58 = key
Expand Down
44 changes: 31 additions & 13 deletions packages/core/src/modules/routing/__tests__/mediation.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport'
import type { InitConfig } from '../../../types'

import { Subject } from 'rxjs'

Expand Down Expand Up @@ -39,14 +40,7 @@ describe('mediator establishment', () => {
await senderAgent?.wallet.delete()
})

test(`Mediation end-to-end flow
1. Start mediator agent and create invitation
2. Start recipient agent with mediatorConnectionsInvite from mediator
3. Assert mediator and recipient are connected and mediation state is Granted
4. Start sender agent and create connection with recipient
5. Assert endpoint in recipient invitation for sender is mediator endpoint
6. Send basic message from sender to recipient and assert it is received on the recipient side
`, async () => {
const e2eMediationTest = async (mediatorAgentConfig: InitConfig, recipientAgentConfig: InitConfig) => {
const mediatorMessages = new Subject<SubjectMessage>()
const recipientMessages = new Subject<SubjectMessage>()
const senderMessages = new Subject<SubjectMessage>()
Expand All @@ -56,8 +50,8 @@ describe('mediator establishment', () => {
'rxjs:sender': senderMessages,
}

// Initialize mediatorReceived message
mediatorAgent = new Agent(mediatorConfig.config, recipientConfig.agentDependencies)
// Initialize mediator
mediatorAgent = new Agent(mediatorAgentConfig, mediatorConfig.agentDependencies)
mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap))
mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages))
await mediatorAgent.initialize()
Expand All @@ -66,17 +60,16 @@ describe('mediator establishment', () => {
const mediatorOutOfBandRecord = await mediatorAgent.oob.createInvitation({
label: 'mediator invitation',
handshake: true,
handshakeProtocols: [HandshakeProtocol.DidExchange],
handshakeProtocols: [HandshakeProtocol.Connections],
})

// Initialize recipient with mediation connections invitation
recipientAgent = new Agent(
{
...recipientConfig.config,
...recipientAgentConfig,
mediatorConnectionsInvite: mediatorOutOfBandRecord.outOfBandInvitation.toUrl({
domain: 'https://example.com/ssi',
}),
mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1,
},
recipientConfig.agentDependencies
)
Expand Down Expand Up @@ -136,6 +129,31 @@ describe('mediator establishment', () => {
})

expect(basicMessage.content).toBe(message)
}

test(`Mediation end-to-end flow
1. Start mediator agent and create invitation
2. Start recipient agent with mediatorConnectionsInvite from mediator
3. Assert mediator and recipient are connected and mediation state is Granted
4. Start sender agent and create connection with recipient
5. Assert endpoint in recipient invitation for sender is mediator endpoint
6. Send basic message from sender to recipient and assert it is received on the recipient side
`, async () => {
await e2eMediationTest(mediatorConfig.config, {
...recipientConfig.config,
mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1,
})
})

test('Mediation end-to-end flow (use did:key in both sides)', async () => {
await e2eMediationTest(
{ ...mediatorConfig.config, useDidKeyInProtocols: true },
{
...recipientConfig.config,
mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1,
useDidKeyInProtocols: true,
}
)
})

test('restart recipient agent and create connection through mediator after recipient agent is restarted', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Expose, Type } from 'class-transformer'
import { IsArray, ValidateNested, IsString, IsEnum, IsInstance } from 'class-validator'
import { Verkey } from 'indy-sdk'

import { AgentMessage } from '../../../agent/AgentMessage'
import { IsValidMessageType, parseMessageType } from '../../../utils/messageType'
Expand All @@ -11,7 +10,7 @@ export enum KeylistUpdateAction {
}

export class KeylistUpdate {
public constructor(options: { recipientKey: Verkey; action: KeylistUpdateAction }) {
public constructor(options: { recipientKey: string; action: KeylistUpdateAction }) {
if (options) {
this.recipientKey = options.recipientKey
this.action = options.action
Expand All @@ -20,7 +19,7 @@ export class KeylistUpdate {

@IsString()
@Expose({ name: 'recipient_key' })
public recipientKey!: Verkey
public recipientKey!: string

@IsEnum(KeylistUpdateAction)
public action!: KeylistUpdateAction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Expose, Type } from 'class-transformer'
import { IsArray, IsEnum, IsInstance, IsString, ValidateNested } from 'class-validator'
import { Verkey } from 'indy-sdk'

import { AgentMessage } from '../../../agent/AgentMessage'
import { IsValidMessageType, parseMessageType } from '../../../utils/messageType'
Expand All @@ -15,7 +14,7 @@ export enum KeylistUpdateResult {
}

export class KeylistUpdated {
public constructor(options: { recipientKey: Verkey; action: KeylistUpdateAction; result: KeylistUpdateResult }) {
public constructor(options: { recipientKey: string; action: KeylistUpdateAction; result: KeylistUpdateResult }) {
if (options) {
this.recipientKey = options.recipientKey
this.action = options.action
Expand All @@ -25,7 +24,7 @@ export class KeylistUpdated {

@IsString()
@Expose({ name: 'recipient_key' })
public recipientKey!: Verkey
public recipientKey!: string

@IsEnum(KeylistUpdateAction)
public action!: KeylistUpdateAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { EncryptedMessage } from '../../../types'
import type { ConnectionRecord } from '../../connections'
import type { Routing } from '../../connections/services/ConnectionService'
import type { MediationStateChangedEvent, KeylistUpdatedEvent } from '../RoutingEvents'
import type { KeylistUpdateResponseMessage, MediationDenyMessage, MediationGrantMessage } from '../messages'
import type { MediationDenyMessage } from '../messages'
import type { StatusMessage, MessageDeliveryMessage } from '../protocol'
import type { GetRoutingOptions } from './RoutingService'

Expand All @@ -21,13 +21,19 @@ import { KeyType } from '../../../crypto'
import { AriesFrameworkError } from '../../../error'
import { injectable } from '../../../plugins'
import { JsonTransformer } from '../../../utils'
import { ConnectionMetadataKeys } from '../../connections/repository/ConnectionMetadataTypes'
import { ConnectionService } from '../../connections/services/ConnectionService'
import { Key } from '../../dids'
import { didKeyToVerkey } from '../../dids/helpers'
import { didKeyToVerkey, isDidKey, verkeyToDidKey } from '../../dids/helpers'
import { ProblemReportError } from '../../problem-reports'
import { RoutingEventTypes } from '../RoutingEvents'
import { RoutingProblemReportReason } from '../error'
import { KeylistUpdateAction, MediationRequestMessage } from '../messages'
import {
KeylistUpdateAction,
KeylistUpdateResponseMessage,
MediationRequestMessage,
MediationGrantMessage,
} from '../messages'
import { KeylistUpdate, KeylistUpdateMessage } from '../messages/KeylistUpdateMessage'
import { MediationRole, MediationState } from '../models'
import { DeliveryRequestMessage, MessagesReceivedMessage, StatusRequestMessage } from '../protocol/pickup/v2/messages'
Expand Down Expand Up @@ -104,6 +110,10 @@ export class MediationRecipientService {
// Update record
mediationRecord.endpoint = messageContext.message.endpoint

// Update connection metadata to use their key format in further protocol messages
const connectionUsesDidKey = messageContext.message.routingKeys.some(isDidKey)
await this.updateUseDidKeysFlag(connection, MediationGrantMessage.type.protocolUri, connectionUsesDidKey)

// According to RFC 0211 keys should be a did key, but base58 encoded verkey was used before
// RFC was accepted. This converts the key to a public key base58 if it is a did key.
mediationRecord.routingKeys = messageContext.message.routingKeys.map(didKeyToVerkey)
Expand All @@ -122,12 +132,16 @@ export class MediationRecipientService {

const keylist = messageContext.message.updated

// Update connection metadata to use their key format in further protocol messages
const connectionUsesDidKey = keylist.some((key) => isDidKey(key.recipientKey))
await this.updateUseDidKeysFlag(connection, KeylistUpdateResponseMessage.type.protocolUri, connectionUsesDidKey)

// update keylist in mediationRecord
for (const update of keylist) {
if (update.action === KeylistUpdateAction.add) {
mediationRecord.addRecipientKey(update.recipientKey)
mediationRecord.addRecipientKey(didKeyToVerkey(update.recipientKey))
} else if (update.action === KeylistUpdateAction.remove) {
mediationRecord.removeRecipientKey(update.recipientKey)
mediationRecord.removeRecipientKey(didKeyToVerkey(update.recipientKey))
}
}

Expand All @@ -146,9 +160,18 @@ export class MediationRecipientService {
verKey: string,
timeoutMs = 15000 // TODO: this should be a configurable value in agent config
): Promise<MediationRecord> {
const message = this.createKeylistUpdateMessage(verKey)
const connection = await this.connectionService.getById(mediationRecord.connectionId)

// Use our useDidKey configuration unless we know the key formatting other party is using
let useDidKey = this.config.useDidKeyInProtocols

const useDidKeysConnectionMetadata = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol)
if (useDidKeysConnectionMetadata) {
useDidKey = useDidKeysConnectionMetadata[KeylistUpdateMessage.type.protocolUri] ?? useDidKey
}

const message = this.createKeylistUpdateMessage(useDidKey ? verkeyToDidKey(verKey) : verKey)

mediationRecord.assertReady()
mediationRecord.assertRole(MediationRole.Recipient)

Expand Down Expand Up @@ -381,6 +404,13 @@ export class MediationRecipientService {
await this.mediationRepository.update(mediationRecord)
}
}

private async updateUseDidKeysFlag(connection: ConnectionRecord, protocolUri: string, connectionUsesDidKey: boolean) {
const useDidKeysForProtocol = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol) ?? {}
useDidKeysForProtocol[protocolUri] = connectionUsesDidKey
connection.metadata.set(ConnectionMetadataKeys.UseDidKeysForProtocol, useDidKeysForProtocol)
await this.connectionService.update(connection)
}
}

export interface MediationProtocolMsgReturnType<MessageType extends AgentMessage> {
Expand Down
29 changes: 25 additions & 4 deletions packages/core/src/modules/routing/services/MediatorService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext'
import type { EncryptedMessage } from '../../../types'
import type { ConnectionRecord } from '../../connections'
import type { MediationStateChangedEvent } from '../RoutingEvents'
import type { ForwardMessage, KeylistUpdateMessage, MediationRequestMessage } from '../messages'
import type { ForwardMessage, MediationRequestMessage } from '../messages'

import { AgentConfig } from '../../../agent/AgentConfig'
import { EventEmitter } from '../../../agent/EventEmitter'
Expand All @@ -10,9 +11,12 @@ import { AriesFrameworkError } from '../../../error'
import { inject, injectable } from '../../../plugins'
import { JsonTransformer } from '../../../utils/JsonTransformer'
import { Wallet } from '../../../wallet/Wallet'
import { didKeyToVerkey } from '../../dids/helpers'
import { ConnectionService } from '../../connections'
import { ConnectionMetadataKeys } from '../../connections/repository/ConnectionMetadataTypes'
import { didKeyToVerkey, isDidKey, verkeyToDidKey } from '../../dids/helpers'
import { RoutingEventTypes } from '../RoutingEvents'
import {
KeylistUpdateMessage,
KeylistUpdateAction,
KeylistUpdateResult,
KeylistUpdated,
Expand All @@ -33,20 +37,23 @@ export class MediatorService {
private mediatorRoutingRepository: MediatorRoutingRepository
private wallet: Wallet
private eventEmitter: EventEmitter
private connectionService: ConnectionService
private _mediatorRoutingRecord?: MediatorRoutingRecord

public constructor(
mediationRepository: MediationRepository,
mediatorRoutingRepository: MediatorRoutingRepository,
agentConfig: AgentConfig,
@inject(InjectionSymbols.Wallet) wallet: Wallet,
eventEmitter: EventEmitter
eventEmitter: EventEmitter,
connectionService: ConnectionService
) {
this.mediationRepository = mediationRepository
this.mediatorRoutingRepository = mediatorRoutingRepository
this.agentConfig = agentConfig
this.wallet = wallet
this.eventEmitter = eventEmitter
this.connectionService = connectionService
}

public async initialize() {
Expand Down Expand Up @@ -114,6 +121,10 @@ export class MediatorService {
mediationRecord.assertReady()
mediationRecord.assertRole(MediationRole.Mediator)

// Update connection metadata to use their key format in further protocol messages
const connectionUsesDidKey = message.updates.some((update) => isDidKey(update.recipientKey))
await this.updateUseDidKeysFlag(connection, KeylistUpdateMessage.type.protocolUri, connectionUsesDidKey)

for (const update of message.updates) {
const updated = new KeylistUpdated({
action: update.action,
Expand Down Expand Up @@ -149,9 +160,12 @@ export class MediatorService {

await this.updateState(mediationRecord, MediationState.Granted)

// Use our useDidKey configuration, as this is the first interaction for this protocol
const useDidKey = this.agentConfig.useDidKeyInProtocols

const message = new MediationGrantMessage({
endpoint: this.agentConfig.endpoints[0],
routingKeys: this.getRoutingKeys(),
routingKeys: useDidKey ? this.getRoutingKeys().map(verkeyToDidKey) : this.getRoutingKeys(),
threadId: mediationRecord.threadId,
})

Expand Down Expand Up @@ -207,4 +221,11 @@ export class MediatorService {
},
})
}

private async updateUseDidKeysFlag(connection: ConnectionRecord, protocolUri: string, connectionUsesDidKey: boolean) {
const useDidKeysForProtocol = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol) ?? {}
useDidKeysForProtocol[protocolUri] = connectionUsesDidKey
connection.metadata.set(ConnectionMetadataKeys.UseDidKeysForProtocol, useDidKeysForProtocol)
await this.connectionService.update(connection)
}
}
Loading

0 comments on commit 8efade5

Please sign in to comment.