Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Use did:peer:4 by default for DID Exchange #2166

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/tests/oob.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ describe('out of band', () => {

// Use the invitation did from the first connection to create the second connection
const outOfBandRecord2 = await faberAgent.modules.oob.createInvitation({
invitationDid: outOfBandRecord1.outOfBandInvitation.invitationDids[0],
invitationDid: faberAliceConnection.did,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is different right? InvitationDid is usually not the final did

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case it should be the same, since we are not setting it as an multiUse invitation. I did this trick because OutOfBandInvitation.invitationdDids is dynamically generating peer:2 DIDs and this does not work directly when querying peer:4 DIDs.

But I'll take a second look to it to see if everything is in place.

})

let { connectionRecord: aliceFaberConnection2 } = await aliceAgent.modules.oob.receiveInvitation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class ConnectionsModuleConfig {

/** See {@link ConnectionsModuleConfigOptions.peerNumAlgoForDidExchangeRequests} */
public get peerNumAlgoForDidExchangeRequests() {
return this.#peerNumAlgoForDidExchangeRequests ?? PeerDidNumAlgo.GenesisDoc
return this.#peerNumAlgoForDidExchangeRequests ?? PeerDidNumAlgo.ShortFormAndLongForm
}

/** See {@link ConnectionsModuleConfigOptions.peerNumAlgoForDidExchangeRequests} */
Expand Down
36 changes: 20 additions & 16 deletions packages/didcomm/src/modules/connections/DidExchangeProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
tryParseDid,
didKeyToInstanceOfKey,
DidRepository,
didKeyToVerkey,
parseDid,
} from '@credo-ts/core'

import { Attachment, AttachmentData } from '../../decorators/attachment/Attachment'
Expand Down Expand Up @@ -284,32 +286,33 @@ export class DidExchangeProtocol {
const didDocument = await createPeerDidFromServices(agentContext, services, numAlgo)
const message = new DidExchangeResponseMessage({ did: didDocument.id, threadId })

// DID Rotate attachment should be signed with invitation keys
const invitationRecipientKeys = outOfBandRecord.outOfBandInvitation
.getInlineServices()
.map((s) => s.recipientKeys)
.reduce((acc, curr) => acc.concat(curr), [])

// In case the DID Exchange started with an implicit invitation, take all recipient keys from the corresponding DID
for (const did of outOfBandRecord.outOfBandInvitation.getDidServices()) {
invitationRecipientKeys.push(
...(await getDidDocumentForCreatedDid(agentContext, parseDid(did).did)).recipientKeys.map(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we always need to do this, or only for implicit? Because now we run this always?

Copy link
Contributor Author

@genaris genaris Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment is misleading. It is useful for resolvable DIDs in general. However I'll review the DID Exchange Response creation more in details just to make sure it works properly in all cases.

(key) => key.publicKeyBase58
)
)
}

if (numAlgo === PeerDidNumAlgo.GenesisDoc) {
message.didDoc = await this.createSignedAttachment(
agentContext,
didDocument.toJSON(),
Array.from(
new Set(
services
.map((s) => s.recipientKeys)
.reduce((acc, curr) => acc.concat(curr), [])
.map((key) => key.publicKeyBase58)
)
)
Array.from(new Set(invitationRecipientKeys.map(didKeyToVerkey)))
)
} else {
// We assume any other case is a resolvable did (e.g. did:peer:2 or did:peer:4)
message.didRotate = await this.createSignedAttachment(
agentContext,
didDocument.id,
Array.from(
new Set(
services
.map((s) => s.recipientKeys)
.reduce((acc, curr) => acc.concat(curr), [])
.map((key) => key.publicKeyBase58)
)
)
Array.from(new Set(invitationRecipientKeys.map(didKeyToVerkey)))
)
}

Expand Down Expand Up @@ -462,6 +465,7 @@ export class DidExchangeProtocol {
data: string | Record<string, unknown>,
verkeys: string[]
) {
this.logger.debug(`Creating signed attachment with keys ${JSON.stringify(verkeys)}`)
const signedAttach = new Attachment({
mimeType: typeof data === 'string' ? undefined : 'application/json',
data: new AttachmentData({
Expand Down
10 changes: 6 additions & 4 deletions packages/didcomm/src/modules/connections/services/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,14 @@ export function routingToServices(routing: Routing): ResolvedDidCommService[] {
}

export async function getDidDocumentForCreatedDid(agentContext: AgentContext, did: string) {
// Ensure that the DID has been created by us
const didRecord = await agentContext.dependencyManager.resolve(DidRepository).findCreatedDid(agentContext, did)

if (!didRecord?.didDocument) {
throw new CredoError(`Could not get DidDocument for created did ${did}`)
if (!didRecord) {
throw new CredoError(`Could not find created did ${did}`)
}
return didRecord.didDocument

const didsApi = agentContext.dependencyManager.resolve(DidsApi)
return await didsApi.resolveDidDocument(did)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm 🤔

Are there cases where we don't store our own did?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, but I wanted to take all the advantages of DidsApi.resolveDidDocument() (such as dynamically generating the did:peer:4 DID Document) making sure that it would only resolve DIDs created by us (I can propagate a filtering parameter but it would modify a good number of methods).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is okay! Just curious :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also has the benefit of caching, and even DIDs created by us, but not managed by us (e.g. think a did web for which we have one key, but it's stored outside of credo as it's regularly updated)

}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/didcomm/src/modules/oob/OutOfBandApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ export class OutOfBandApi {

const invitation = new OutOfBandInvitation({
id: config.did,
label: config.label ?? '',
label: config.alias ?? '',
services: [config.did],
handshakeProtocols,
})
Expand Down
28 changes: 12 additions & 16 deletions packages/didcomm/src/modules/oob/__tests__/implicit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ const aliceAgentOptions = getInMemoryAgentOptions(
)

describe('out of band implicit', () => {
let faberAgent: Agent
let aliceAgent: Agent
let faberAgent: Agent<typeof faberAgentOptions.modules>
let aliceAgent: Agent<typeof aliceAgentOptions.modules>

beforeAll(async () => {
faberAgent = new Agent(faberAgentOptions)
Expand Down Expand Up @@ -80,7 +80,7 @@ describe('out of band implicit', () => {
let { connectionRecord: aliceFaberConnection } = await aliceAgent.modules.oob.receiveImplicitInvitation({
did: inMemoryDid,
alias: 'Faber public',
label: 'Alice',
label: 'Custom Alice',
handshakeProtocols: [HandshakeProtocol.DidExchange],
})

Expand All @@ -96,8 +96,8 @@ describe('out of band implicit', () => {

expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection)
expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection)
expect(faberAliceConnection.theirLabel).toBe('Alice')
expect(aliceFaberConnection.alias).toBe('Faber public')
expect(faberAliceConnection.theirLabel).toBe('Custom Alice')
expect(aliceFaberConnection.theirLabel).toBe('Faber public')
expect(aliceFaberConnection.invitationDid).toBe(inMemoryDid)

// It is possible for an agent to check if it has already a connection to a certain public entity
Expand All @@ -112,7 +112,6 @@ describe('out of band implicit', () => {
let { connectionRecord: aliceFaberConnection } = await aliceAgent.modules.oob.receiveImplicitInvitation({
did: serviceUrl,
alias: 'Faber public',
label: 'Alice',
handshakeProtocols: [HandshakeProtocol.DidExchange],
})

Expand All @@ -128,8 +127,8 @@ describe('out of band implicit', () => {

expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection)
expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection)
expect(faberAliceConnection.theirLabel).toBe('Alice')
expect(aliceFaberConnection.alias).toBe('Faber public')
expect(faberAliceConnection.theirLabel).toBe(aliceAgent.config.label)
expect(aliceFaberConnection.theirLabel).toBe('Faber public')
expect(aliceFaberConnection.invitationDid).toBe(serviceUrl)

// It is possible for an agent to check if it has already a connection to a certain public entity
Expand All @@ -142,7 +141,6 @@ describe('out of band implicit', () => {
let { connectionRecord: aliceFaberConnection } = await aliceAgent.modules.oob.receiveImplicitInvitation({
did: inMemoryDid,
alias: 'Faber public',
label: 'Alice',
handshakeProtocols: [HandshakeProtocol.Connections],
})

Expand All @@ -158,8 +156,8 @@ describe('out of band implicit', () => {

expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection)
expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection)
expect(faberAliceConnection.theirLabel).toBe('Alice')
expect(aliceFaberConnection.alias).toBe('Faber public')
expect(faberAliceConnection.theirLabel).toBe(aliceAgent.config.label)
expect(aliceFaberConnection.theirLabel).toBe('Faber public')
expect(aliceFaberConnection.invitationDid).toBe(inMemoryDid)

// It is possible for an agent to check if it has already a connection to a certain public entity
Expand All @@ -171,7 +169,6 @@ describe('out of band implicit', () => {
aliceAgent.modules.oob.receiveImplicitInvitation({
did: 'did:sov:ZSEqSci581BDZCFPa29ScB',
alias: 'Faber public',
label: 'Alice',
handshakeProtocols: [HandshakeProtocol.DidExchange],
})
).rejects.toThrow(/Unable to resolve did/)
Expand All @@ -183,7 +180,6 @@ describe('out of band implicit', () => {
let { connectionRecord: aliceFaberConnection } = await aliceAgent.modules.oob.receiveImplicitInvitation({
did: inMemoryDid,
alias: 'Faber public',
label: 'Alice',
handshakeProtocols: [HandshakeProtocol.Connections],
})

Expand All @@ -199,8 +195,8 @@ describe('out of band implicit', () => {

expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection)
expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection)
expect(faberAliceConnection.theirLabel).toBe('Alice')
expect(aliceFaberConnection.alias).toBe('Faber public')
expect(faberAliceConnection.theirLabel).toBe(aliceAgent.config.label)
expect(aliceFaberConnection.theirLabel).toBe('Faber public')
expect(aliceFaberConnection.invitationDid).toBe(inMemoryDid)

// Repeat implicit invitation procedure
Expand All @@ -224,7 +220,7 @@ describe('out of band implicit', () => {
expect(aliceFaberNewConnection).toBeConnectedWith(faberAliceNewConnection)
expect(faberAliceNewConnection).toBeConnectedWith(aliceFaberNewConnection)
expect(faberAliceNewConnection.theirLabel).toBe('Alice New')
expect(aliceFaberNewConnection.alias).toBe('Faber public New')
expect(aliceFaberNewConnection.theirLabel).toBe('Faber public New')
expect(aliceFaberNewConnection.invitationDid).toBe(inMemoryDid)

// Both connections will be associated to the same invitation did
Expand Down
Loading