Skip to content

Commit

Permalink
feat: find existing connection based on invitation did (#698)
Browse files Browse the repository at this point in the history
* Extract creation of did doc from services
* Create peer did from service and store it as invitation did

Signed-off-by: Jakub Koci <[email protected]>
  • Loading branch information
jakubkoci committed May 12, 2022
1 parent 1bbe3a4 commit d279e48
Show file tree
Hide file tree
Showing 12 changed files with 284 additions and 110 deletions.
4 changes: 4 additions & 0 deletions packages/core/src/modules/connections/ConnectionsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ export class ConnectionsModule {
return this.connectionService.findByTheirDid(did)
}

public async findByInvitationDid(invitationDid: string): Promise<ConnectionRecord[]> {
return this.connectionService.findByInvitationDid(invitationDid)
}

private registerHandlers(dispatcher: Dispatcher) {
dispatcher.registerHandler(
new ConnectionRequestHandler(
Expand Down
62 changes: 9 additions & 53 deletions packages/core/src/modules/connections/DidExchangeProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { OutOfBandRecord } from '../oob/repository'
import type { ConnectionRecord } from './repository'
import type { Routing } from './services/ConnectionService'

import { convertPublicKeyToX25519 } from '@stablelib/ed25519'
import { Lifecycle, scoped } from 'tsyringe'

import { AgentConfig } from '../../agent/AgentConfig'
Expand All @@ -14,12 +13,10 @@ import { Attachment, AttachmentData } from '../../decorators/attachment/Attachme
import { AriesFrameworkError } from '../../error'
import { JsonEncoder } from '../../utils/JsonEncoder'
import { JsonTransformer } from '../../utils/JsonTransformer'
import { uuid } from '../../utils/uuid'
import { DidCommService, DidDocument, DidDocumentBuilder, Key } from '../dids'
import { DidCommService, DidDocument, Key } from '../dids'
import { DidDocumentRole } from '../dids/domain/DidDocumentRole'
import { createDidDocumentFromServices } from '../dids/domain/createPeerDidFromServices'
import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type'
import { getEd25519VerificationMethod } from '../dids/domain/key-type/ed25519'
import { getX25519VerificationMethod } from '../dids/domain/key-type/x25519'
import { DidKey } from '../dids/methods/key/DidKey'
import { DidPeer, PeerDidNumAlgo } from '../dids/methods/peer/DidPeer'
import { DidRecord, DidRepository } from '../dids/repository'
Expand Down Expand Up @@ -72,6 +69,11 @@ export class DidExchangeProtocol {
const { alias, goal, goalCode, routing, autoAcceptConnection } = params

const { did, mediatorId } = routing

// TODO: We should store only one did that we'll use to send the request message with success.
// We take just the first one for now.
const [invitationDid] = outOfBandMessage.invitationDids

const connectionRecord = await this.connectionService.createConnection({
protocol: HandshakeProtocol.DidExchange,
role: DidExchangeRole.Requester,
Expand All @@ -83,6 +85,7 @@ export class DidExchangeProtocol {
mediatorId,
autoAcceptConnection: outOfBandRecord.autoAcceptConnection,
outOfBandId: outOfBandRecord.id,
invitationDid,
})

DidExchangeStateMachine.assertCreateMessageState(DidExchangeRequestMessage.type, connectionRecord)
Expand Down Expand Up @@ -374,54 +377,7 @@ export class DidExchangeProtocol {
}

private async createPeerDidDoc(services: DidCommService[]) {
const didDocumentBuilder = new DidDocumentBuilder('')

// We need to all reciepient and routing keys from all services but we don't want to duplicated items
const recipientKeys = new Set(services.map((s) => s.recipientKeys).reduce((acc, curr) => acc.concat(curr), []))
const routingKeys = new Set(
services
.map((s) => s.routingKeys)
.filter((r): r is string[] => r !== undefined)
.reduce((acc, curr) => acc.concat(curr), [])
)

for (const recipientKey of recipientKeys) {
const publicKeyBase58 = recipientKey
const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519)
const x25519Key = Key.fromPublicKey(convertPublicKeyToX25519(ed25519Key.publicKey), KeyType.X25519)

const ed25519VerificationMethod = getEd25519VerificationMethod({
id: uuid(),
key: ed25519Key,
controller: '#id',
})
const x25519VerificationMethod = getX25519VerificationMethod({
id: uuid(),
key: x25519Key,
controller: '#id',
})

// We should not add duplicated keys for services
didDocumentBuilder.addAuthentication(ed25519VerificationMethod).addKeyAgreement(x25519VerificationMethod)
}

for (const routingKey of routingKeys) {
const publicKeyBase58 = routingKey
const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519)
const verificationMethod = getEd25519VerificationMethod({
id: uuid(),
key: ed25519Key,
controller: '#id',
})
didDocumentBuilder.addVerificationMethod(verificationMethod)
}

services.forEach((service) => {
didDocumentBuilder.addService(service)
})

const didDocument = didDocumentBuilder.build()

const didDocument = createDidDocumentFromServices(services)
const peerDid = DidPeer.fromDidDocument(didDocument, PeerDidNumAlgo.GenesisDoc)

const didRecord = new DidRecord({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface ConnectionRecordProps {
errorMessage?: string
protocol?: HandshakeProtocol
outOfBandId?: string
invitationDid?: string
}

export type CustomConnectionTags = TagsBase
Expand Down Expand Up @@ -59,6 +60,7 @@ export class ConnectionRecord
public errorMessage?: string
public protocol?: HandshakeProtocol
public outOfBandId?: string
public invitationDid?: string

public static readonly type = 'ConnectionRecord'
public readonly type = ConnectionRecord.type
Expand All @@ -70,6 +72,7 @@ export class ConnectionRecord
this.id = props.id ?? uuid()
this.createdAt = props.createdAt ?? new Date()
this.did = props.did
this.invitationDid = props.invitationDid
this.theirDid = props.theirDid
this.theirLabel = props.theirLabel
this.state = props.state
Expand Down Expand Up @@ -97,6 +100,7 @@ export class ConnectionRecord
did: this.did,
theirDid: this.theirDid,
outOfBandId: this.outOfBandId,
invitationDid: this.invitationDid,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ export class ConnectionService {

const { did, mediatorId } = config.routing
const didDoc = this.createDidDoc(config.routing)

// TODO: We should store only one did that we'll use to send the request message with success.
// We take just the first one for now.
const [invitationDid] = outOfBandMessage.invitationDids

const connectionRecord = await this.createConnection({
protocol: HandshakeProtocol.Connections,
role: ConnectionRole.Invitee,
Expand All @@ -108,8 +113,9 @@ export class ConnectionService {
mediatorId,
autoAcceptConnection: config?.autoAcceptConnection,
multiUseInvitation: false,
outOfBandId: outOfBandRecord.id,
invitationDid,
})
connectionRecord.outOfBandId = outOfBandRecord.id

const routing = config.routing
const { did: peerDid } = await this.createDid({
Expand Down Expand Up @@ -631,6 +637,10 @@ export class ConnectionService {
return this.connectionRepository.findSingleByQuery({ outOfBandId })
}

public async findByInvitationDid(invitationDid: string) {
return this.connectionRepository.findByQuery({ invitationDid })
}

public async createConnection(options: {
role?: ConnectionRole | DidExchangeRole
state?: ConnectionState | DidExchangeState
Expand All @@ -644,6 +654,7 @@ export class ConnectionService {
imageUrl?: string
protocol?: HandshakeProtocol
outOfBandId?: string
invitationDid?: string
}): Promise<ConnectionRecord> {
const connectionRecord = new ConnectionRecord({
did: options.did,
Expand All @@ -658,6 +669,7 @@ export class ConnectionService {
mediatorId: options.mediatorId,
protocol: options.protocol,
outOfBandId: options.outOfBandId,
invitationDid: options.invitationDid,
})
await this.connectionRepository.save(connectionRecord)
return connectionRecord
Expand Down
61 changes: 61 additions & 0 deletions packages/core/src/modules/dids/domain/createPeerDidFromServices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { DidCommService } from '.'

import { convertPublicKeyToX25519 } from '@stablelib/ed25519'

import { KeyType } from '../../../crypto'
import { uuid } from '../../../utils/uuid'

import { DidDocumentBuilder } from './DidDocumentBuilder'
import { Key } from './Key'
import { getEd25519VerificationMethod } from './key-type/ed25519'
import { getX25519VerificationMethod } from './key-type/x25519'

export function createDidDocumentFromServices(services: DidCommService[]) {
const didDocumentBuilder = new DidDocumentBuilder('')

// We need to all reciepient and routing keys from all services but we don't want to duplicated items
const recipientKeys = new Set(services.map((s) => s.recipientKeys).reduce((acc, curr) => acc.concat(curr), []))
const routingKeys = new Set(
services
.map((s) => s.routingKeys)
.filter((r): r is string[] => r !== undefined)
.reduce((acc, curr) => acc.concat(curr), [])
)

for (const recipientKey of recipientKeys) {
const publicKeyBase58 = recipientKey
const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519)
const x25519Key = Key.fromPublicKey(convertPublicKeyToX25519(ed25519Key.publicKey), KeyType.X25519)

const ed25519VerificationMethod = getEd25519VerificationMethod({
id: uuid(),
key: ed25519Key,
controller: '#id',
})
const x25519VerificationMethod = getX25519VerificationMethod({
id: uuid(),
key: x25519Key,
controller: '#id',
})

// We should not add duplicated keys for services
didDocumentBuilder.addAuthentication(ed25519VerificationMethod).addKeyAgreement(x25519VerificationMethod)
}

for (const routingKey of routingKeys) {
const publicKeyBase58 = routingKey
const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519)
const verificationMethod = getEd25519VerificationMethod({
id: uuid(),
key: ed25519Key,
controller: '#id',
})
didDocumentBuilder.addVerificationMethod(verificationMethod)
}

services.forEach((service) => {
didDocumentBuilder.addService(service)
})

return didDocumentBuilder.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { JsonTransformer } from '../../../../../utils'
import didKeyBls12381g1 from '../../../__tests__/__fixtures__/didKeyBls12381g1.json'
import didKeyBls12381g1g2 from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json'
import didKeyBls12381g2 from '../../../__tests__/__fixtures__/didKeyBls12381g2.json'
import didKeyEd25519 from '../../../__tests__/__fixtures__/didKeyEd25519.json'
import didKeyX25519 from '../../../__tests__/__fixtures__/didKeyX25519.json'
import { DidDocument, Key } from '../../../domain'
import { DidPeer, PeerDidNumAlgo } from '../DidPeer'
import { serviceToNumAlgo2Did } from '../peerDidNumAlgo2'

import didPeer1zQmRDidCommServices from './__fixtures__/didPeer1zQmR-did-comm-service.json'
import didPeer1zQmR from './__fixtures__/didPeer1zQmR.json'
import didPeer1zQmZ from './__fixtures__/didPeer1zQmZ.json'
import didPeer2Ez6L from './__fixtures__/didPeer2Ez6L.json'

describe('DidPeer', () => {
test('transforms a key correctly into a peer did method 0 did document', async () => {
const didDocuments = [didKeyEd25519, didKeyBls12381g1, didKeyX25519, didKeyBls12381g1g2, didKeyBls12381g2]

for (const didDocument of didDocuments) {
const key = Key.fromFingerprint(didDocument.id.split(':')[2])

const didPeer = DidPeer.fromKey(key)
const expectedDidPeerDocument = JSON.parse(
JSON.stringify(didDocument).replace(new RegExp('did:key:', 'g'), 'did:peer:0')
)

expect(didPeer.didDocument.toJSON()).toMatchObject(expectedDidPeerDocument)
}
})

test('transforms a method 2 did correctly into a did document', () => {
expect(DidPeer.fromDid(didPeer2Ez6L.id).didDocument.toJSON()).toMatchObject(didPeer2Ez6L)
})

test('transforms a method 0 did correctly into a did document', () => {
const didDocuments = [didKeyEd25519, didKeyBls12381g1, didKeyX25519, didKeyBls12381g1g2, didKeyBls12381g2]

for (const didDocument of didDocuments) {
const didPeer = DidPeer.fromDid(didDocument.id.replace('did:key:', 'did:peer:0'))
const expectedDidPeerDocument = JSON.parse(
JSON.stringify(didDocument).replace(new RegExp('did:key:', 'g'), 'did:peer:0')
)

expect(didPeer.didDocument.toJSON()).toMatchObject(expectedDidPeerDocument)
}
})

test('transforms a did document into a valid method 2 did', () => {
const didPeer2 = DidPeer.fromDidDocument(
JsonTransformer.fromJSON(didPeer2Ez6L, DidDocument),
PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc
)

expect(didPeer2.did).toBe(didPeer2Ez6L.id)
})

test('transforms a did comm service into a valid method 2 did', () => {
const didDocument = JsonTransformer.fromJSON(didPeer1zQmRDidCommServices, DidDocument)
const peerDid = serviceToNumAlgo2Did(didDocument.didCommServices[0])
const peerDidInstance = DidPeer.fromDid(peerDid)

// TODO the following `console.log` statement throws an error "TypeError: Cannot read property 'toLowerCase'
// of undefined" because of this:
//
// `service.id = `${did}#${service.type.toLowerCase()}-${serviceIndex++}``

// console.log(peerDidInstance.didDocument)

expect(peerDid).toBe(
'did:peer:2.Ez6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH.SeyJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9'
)
expect(peerDid).toBe(peerDidInstance.did)
})

test('transforms a did document into a valid method 1 did', () => {
const didPeer1 = DidPeer.fromDidDocument(
JsonTransformer.fromJSON(didPeer1zQmR, DidDocument),
PeerDidNumAlgo.GenesisDoc
)

expect(didPeer1.did).toBe(didPeer1zQmR.id)
})

// FIXME: we need some input data from AFGO for this test to succeed (we create a hash of the document, so any inconsistency is fatal)
xtest('transforms a did document from aries-framework-go into a valid method 1 did', () => {
const didPeer1 = DidPeer.fromDidDocument(
JsonTransformer.fromJSON(didPeer1zQmZ, DidDocument),
PeerDidNumAlgo.GenesisDoc
)

expect(didPeer1.did).toBe(didPeer1zQmZ.id)
})

test('extracts the numAlgo from the peer did', async () => {
// NumAlgo 0
const key = Key.fromFingerprint(didKeyEd25519.id.split(':')[2])
const didPeerNumAlgo0 = DidPeer.fromKey(key)

expect(didPeerNumAlgo0.numAlgo).toBe(PeerDidNumAlgo.InceptionKeyWithoutDoc)
expect(DidPeer.fromDid('did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL').numAlgo).toBe(
PeerDidNumAlgo.InceptionKeyWithoutDoc
)

// NumAlgo 1
const peerDidNumAlgo1 = 'did:peer:1zQmZMygzYqNwU6Uhmewx5Xepf2VLp5S4HLSwwgf2aiKZuwa'
expect(DidPeer.fromDid(peerDidNumAlgo1).numAlgo).toBe(PeerDidNumAlgo.GenesisDoc)

// NumAlgo 2
const peerDidNumAlgo2 =
'did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0'
expect(DidPeer.fromDid(peerDidNumAlgo2).numAlgo).toBe(PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc)
expect(DidPeer.fromDidDocument(JsonTransformer.fromJSON(didPeer2Ez6L, DidDocument)).numAlgo).toBe(
PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"@context": ["https://w3id.org/did/v1"],
"id": "did:peer:1zQmRYBx1pL86DrsxoJ2ZD3w42d7Ng92ErPgFsCSqg8Q1h4i",
"keyAgreement": [
{
"id": "#6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V",
"type": "Ed25519VerificationKey2018",
"publicKeyBase58": "ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7"
}
],
"service": [
{
"id": "#service-0",
"type": "did-communication",
"serviceEndpoint": "https://example.com/endpoint",
"recipientKeys": ["did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"],
"routingKeys": ["ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7"],
"accept": ["didcomm/v2", "didcomm/aip2;env=rfc587"]
}
]
}
Loading

0 comments on commit d279e48

Please sign in to comment.