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: did:peer:2 and did:peer:4 support in DID Exchange #1550

Merged
merged 5 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions packages/core/src/modules/connections/DidExchangeProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export class DidExchangeProtocol {
const didDocument = await this.resolveDidDocument(agentContext, message)

const didRecord = new DidRecord({
did: message.did,
did: didDocument.id,
role: DidDocumentRole.Received,
// It is important to take the did document from the PeerDid class
// as it will have the id property
Expand All @@ -186,6 +186,7 @@ export class DidExchangeProtocol {
// 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),
alsoKnownAs: didDocument.alsoKnownAs,
},
})

Expand Down Expand Up @@ -330,13 +331,14 @@ export class DidExchangeProtocol {
)

const didRecord = new DidRecord({
did: message.did,
did: didDocument.id,
role: DidDocumentRole.Received,
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),
alsoKnownAs: didDocument.alsoKnownAs,
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this have any security implications? I could include a did here i don't control and if the also known as was used for querying i could use another did to make it look like i also control the other did.

There should be some sort of verification between these I think if we want to use it for this use case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I need to think a bit more about that, because it should not be a problem when dealing with peer dids (as it is our agent who will be generating the didDoc based on the spec, and the alsoKnownAs is only populated where applicable) but it might be an issue in case we get a DID Exchange Request using a public/custom did.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the context of did:peer helps answer this question as @genaris calls out but just to add color in the general case, if the DID identified in the alsoKnownAs list is resolved and the original DID is found in that DIDs alsoKnownAs list, this gives us certainty that the owner of this DID controls the other DID.

For better worded color lol, consider this note from the DID Core spec on AKA:

Applications might choose to consider two identifiers related by alsoKnownAs to be equivalent if the alsoKnownAs relationship is reciprocated in the reverse direction. It is best practice not to consider them equivalent in the absence of this inverse relationship. In other words, the presence of an alsoKnownAs assertion does not prove that this assertion is true. Therefore, it is strongly advised that a requesting party obtain independent verification of an alsoKnownAs assertion.

Given that the DID subject might use different identifiers for different purposes, an expectation of strong equivalence between the two identifiers, or merging the information of the two corresponding DID documents, is not necessarily appropriate, even with a reciprocal relationship.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for popping in here @dbluhm!

So we need to make sure we've verified the relation of alsoKnownAs both ways before we allow it to be queried.

Maybe we can rename it to verifiedAlsoKnownAs or something different like relatedDids you mentioned ariel.

},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ describe('Did Exchange numalgo settings', () => {
})
})

test('Connect using default setting for requester and numalgo 4 for responder', async () => {
await didExchangeNumAlgoBaseTest({ responderNumAlgoSetting: PeerDidNumAlgo.ShortFormAndLongForm })
})

test('Connect using numalgo 4 for requester and default setting for responder', async () => {
await didExchangeNumAlgoBaseTest({ requesterNumAlgoSetting: PeerDidNumAlgo.ShortFormAndLongForm })
})

test.only('Connect using numalgo 4 for both requester and responder', async () => {
await didExchangeNumAlgoBaseTest({
requesterNumAlgoSetting: PeerDidNumAlgo.ShortFormAndLongForm,
responderNumAlgoSetting: PeerDidNumAlgo.ShortFormAndLongForm,
})
})

test('Connect using an externally defined did for the requested', async () => {
await didExchangeNumAlgoBaseTest({
createExternalDidForRequester: true,
Expand Down Expand Up @@ -140,7 +155,11 @@ async function didExchangeNumAlgoBaseTest(options: {
])
didDocument.id = ourDid

await didRegistry.create(aliceAgent.context, { did: ourDid, didDocument })
await aliceAgent.dids.create({
method: 'inmemory',
did: ourDid,
didDocument,
})
}

let { connectionRecord: aliceConnectionRecord } = await aliceAgent.oob.receiveInvitation(
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/modules/dids/DidsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export class DidsApi {
existingDidRecord.didDocument = didDocument
existingDidRecord.setTags({
recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint),
alsoKnownAs: didDocument.alsoKnownAs,
})

await this.didRepository.update(this.agentContext, existingDidRecord)
Expand All @@ -169,6 +170,7 @@ export class DidsApi {
didDocument,
tags: {
recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint),
alsoKnownAs: didDocument.alsoKnownAs,
},
})
}
Expand Down
46 changes: 45 additions & 1 deletion packages/core/src/modules/dids/__tests__/DidsApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ import { IndySdkModule } from '../../../../../indy-sdk/src'
import { indySdk } from '../../../../tests'
import { getAgentOptions } from '../../../../tests/helpers'
import { Agent } from '../../../agent/Agent'
import { isLongFormDidPeer4, isShortFormDidPeer4 } from '../methods/peer/peerDidNumAlgo4'

import { DidDocument, DidDocumentService, KeyType, TypedArrayEncoder } from '@aries-framework/core'
import {
DidDocument,
DidDocumentService,
KeyType,
PeerDidNumAlgo,
TypedArrayEncoder,
createPeerDidDocumentFromServices,
} from '@aries-framework/core'

const agentOptions = getAgentOptions(
'DidsApi',
Expand Down Expand Up @@ -227,4 +235,40 @@ describe('DidsApi', () => {
],
})
})

test('create and resolve did:peer:4 in short and long form', async () => {
const routing = await agent.mediationRecipient.getRouting({})
const didDocument = createPeerDidDocumentFromServices([
{
id: 'didcomm',
recipientKeys: [routing.recipientKey],
routingKeys: routing.routingKeys,
serviceEndpoint: routing.endpoints[0],
},
])

const result = await agent.dids.create({
method: 'peer',
didDocument,
options: {
numAlgo: PeerDidNumAlgo.ShortFormAndLongForm,
},
})

const longFormDid = result.didState.did
const shortFormDid = result.didState.didDocument?.alsoKnownAs
? result.didState.didDocument?.alsoKnownAs[0]
: undefined

if (!longFormDid) fail('Long form did not defined')
if (!shortFormDid) fail('Short form did not defined')

expect(isLongFormDidPeer4(longFormDid)).toBeTruthy()
expect(isShortFormDidPeer4(shortFormDid)).toBeTruthy()

const didDocumentFromLongFormDid = await agent.dids.resolveDidDocument(longFormDid)
const didDocumentFromShortFormDid = await agent.dids.resolveDidDocument(shortFormDid)

expect(didDocumentFromLongFormDid).toEqual(didDocumentFromShortFormDid)
})
})
42 changes: 38 additions & 4 deletions packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,22 @@ import { PeerDidNumAlgo } from './didPeer'
import { keyToNumAlgo0DidDocument } from './peerDidNumAlgo0'
import { didDocumentJsonToNumAlgo1Did } from './peerDidNumAlgo1'
import { didDocumentToNumAlgo2Did } from './peerDidNumAlgo2'
import { didDocumentToNumAlgo4Did } from './peerDidNumAlgo4'

export class PeerDidRegistrar implements DidRegistrar {
public readonly supportedMethods = ['peer']

public async create(
agentContext: AgentContext,
options: PeerDidNumAlgo0CreateOptions | PeerDidNumAlgo1CreateOptions | PeerDidNumAlgo2CreateOptions
options:
| PeerDidNumAlgo0CreateOptions
| PeerDidNumAlgo1CreateOptions
| PeerDidNumAlgo2CreateOptions
| PeerDidNumAlgo4CreateOptions
): Promise<DidCreateResult> {
const didRepository = agentContext.dependencyManager.resolve(DidRepository)

let did: string
let didDocument: DidDocument

try {
Expand Down Expand Up @@ -50,16 +57,27 @@ export class PeerDidRegistrar implements DidRegistrar {
// TODO: validate did:peer document

didDocument = keyToNumAlgo0DidDocument(key)
did = didDocument.id
} else if (isPeerDidNumAlgo1CreateOptions(options)) {
const didDocumentJson = options.didDocument.toJSON()
const did = didDocumentJsonToNumAlgo1Did(didDocumentJson)
did = didDocumentJsonToNumAlgo1Did(didDocumentJson)

didDocument = JsonTransformer.fromJSON({ ...didDocumentJson, id: did }, DidDocument)
} else if (isPeerDidNumAlgo2CreateOptions(options)) {
const didDocumentJson = options.didDocument.toJSON()
const did = didDocumentToNumAlgo2Did(options.didDocument)
did = didDocumentToNumAlgo2Did(options.didDocument)

didDocument = JsonTransformer.fromJSON({ ...didDocumentJson, id: did }, DidDocument)
} else if (isPeerDidNumAlgo4CreateOptions(options)) {
const didDocumentJson = options.didDocument.toJSON()

const { longFormDid, shortFormDid } = didDocumentToNumAlgo4Did(options.didDocument)

did = longFormDid
didDocument = JsonTransformer.fromJSON(
{ ...didDocumentJson, id: longFormDid, alsoKnownAs: [shortFormDid] },
DidDocument
)
} else {
return {
didDocumentMetadata: {},
Expand All @@ -73,13 +91,14 @@ export class PeerDidRegistrar implements DidRegistrar {

// Save the did so we know we created it and can use it for didcomm
const didRecord = new DidRecord({
did: didDocument.id,
did,
role: DidDocumentRole.Created,
didDocument: isPeerDidNumAlgo1CreateOptions(options) ? didDocument : undefined,
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),
alsoKnownAs: didDocument.alsoKnownAs,
},
})
await didRepository.save(agentContext, didRecord)
Expand Down Expand Up @@ -149,10 +168,15 @@ function isPeerDidNumAlgo2CreateOptions(options: PeerDidCreateOptions): options
return options.options.numAlgo === PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc
}

function isPeerDidNumAlgo4CreateOptions(options: PeerDidCreateOptions): options is PeerDidNumAlgo4CreateOptions {
return options.options.numAlgo === PeerDidNumAlgo.ShortFormAndLongForm
}

export type PeerDidCreateOptions =
| PeerDidNumAlgo0CreateOptions
| PeerDidNumAlgo1CreateOptions
| PeerDidNumAlgo2CreateOptions
| PeerDidNumAlgo4CreateOptions

export interface PeerDidNumAlgo0CreateOptions extends DidCreateOptions {
method: 'peer'
Expand Down Expand Up @@ -188,6 +212,16 @@ export interface PeerDidNumAlgo2CreateOptions extends DidCreateOptions {
secret?: undefined
}

export interface PeerDidNumAlgo4CreateOptions extends DidCreateOptions {
method: 'peer'
did?: never
didDocument: DidDocument
options: {
numAlgo: PeerDidNumAlgo.ShortFormAndLongForm
}
secret?: undefined
}

// Update and Deactivate not supported for did:peer
export type PeerDidUpdateOptions = never
export type PeerDidDeactivateOptions = never
16 changes: 15 additions & 1 deletion packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DidRepository } from '../../repository'
import { getNumAlgoFromPeerDid, isValidPeerDid, PeerDidNumAlgo } from './didPeer'
import { didToNumAlgo0DidDocument } from './peerDidNumAlgo0'
import { didToNumAlgo2DidDocument } from './peerDidNumAlgo2'
import { didToNumAlgo4DidDocument, isShortFormDidPeer4 } from './peerDidNumAlgo4'

export class PeerDidResolver implements DidResolver {
public readonly supportedMethods = ['peer']
Expand Down Expand Up @@ -48,9 +49,22 @@ export class PeerDidResolver implements DidResolver {
didDocument = didDocumentRecord.didDocument
}
// For Method 2, generate from did
else {
else if (numAlgo === PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc) {
didDocument = didToNumAlgo2DidDocument(did)
}
// For Method 4, if short form is received, attempt to get the didDocument from stored record
else {
if (isShortFormDidPeer4(did)) {
const [didRecord] = await didRepository.findAllByDid(agentContext, did)

if (!didRecord) {
throw new AriesFrameworkError(`No did record found for peer did ${did}.`)
}
didDocument = didToNumAlgo4DidDocument(didRecord.did)
} else {
didDocument = didToNumAlgo4DidDocument(did)
}
}

return {
didDocument,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,21 @@ describe('didPeer', () => {
'did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0'
)
).toBe(true)
expect(
isValidPeerDid(
'did:peer:4zQmXU3HDFaMvdiuUh7eC2hUzFxZHgaKUJpiCAkSDfRE6qSn:z2gxx5mnuv7Tuc5GxjJ3BgJ69g1ucM27iVW9xYSg9tbBjjGLKsWGSpEwqQPbCdCt4qs1aoB3HSM4eoUQALBvR52hCEq2quLwo5RzuZBjZZmuNf6FXvVCrRLQdMG52QJ285W5MUd3hK9QGCUoCNAHJprhtpvcJpoohcg5otvuHeZiffYDRWrfxKUGS83X4X7Hp2vYqdFPgBQcwoveyJcyYByu7zT3Fn8faMffCE5oP125gwsHxjkquEnCy3RMbf64NVL9bLDDk391k7W4HyScbLyh7ooJcWaDDjiFMtoi1J856cDocYtxZ7rjmWmG15pgTcBLX7o8ebKhWCrFSMWtspRuKs9VFaY366Sjce5ZxTUsBWUMCpWhQZxeZQ2h42UST5XiJJ7TV1E13a3ttWrHijPcHgX1MvvDAPGKVgU2jXSgH8bCL4mKuVjdEm4Kx5wMdDW88ougUFuLfwhXkDfP7sYAfuaCFWx286kWqkfYdopcGntPjCvDu6uonghRmxeC2qNfXkYmk3ZQJXzsxgQToixevEvfxQgFY1uuNo5288zJPQcfLHtTvgxEhHxD5wwYYeGFqgV6FTg9mZVU5xqg7w6456cLuZNPuARkfpZK78xMEUHtnr95tK91UY'
)
).toBe(true)
expect(isValidPeerDid('did:peer:4zQmXU3HDFaMvdiuUh7eC2hUzFxZHgaKUJpiCAkSDfRE6qSn')).toBe(true)
expect(
isValidPeerDid(
'did:peer:4z2gxx5mnuv7Tuc5GxjJ3BgJ69g1ucM27iVW9xYSg9tbBjjGLKsWGSpEwqQPbCdCt4qs1aoB3HSM4eoUQALBvR52hCEq2quLwo5RzuZBjZZmuNf6FXvVCrRLQdMG52QJ285W5MUd3hK9QGCUoCNAHJprhtpvcJpoohcg5otvuHeZiffYDRWrfxKUGS83X4X7Hp2vYqdFPgBQcwoveyJcyYByu7zT3Fn8faMffCE5oP125gwsHxjkquEnCy3RMbf64NVL9bLDDk391k7W4HyScbLyh7ooJcWaDDjiFMtoi1J856cDocYtxZ7rjmWmG15pgTcBLX7o8ebKhWCrFSMWtspRuKs9VFaY366Sjce5ZxTUsBWUMCpWhQZxeZQ2h42UST5XiJJ7TV1E13a3ttWrHijPcHgX1MvvDAPGKVgU2jXSgH8bCL4mKuVjdEm4Kx5wMdDW88ougUFuLfwhXkDfP7sYAfuaCFWx286kWqkfYdopcGntPjCvDu6uonghRmxeC2qNfXkYmk3ZQJXzsxgQToixevEvfxQgFY1uuNo5288zJPQcfLHtTvgxEhHxD5wwYYeGFqgV6FTg9mZVU5xqg7w6456cLuZNPuARkfpZK78xMEUHtnr95tK91UY'
)
).toBe(false)

expect(
isValidPeerDid(
'did:peer:4.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0'
'did:peer:5.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0'
)
).toBe(false)
})
Expand All @@ -35,6 +46,13 @@ describe('didPeer', () => {
'did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0'
)
).toBe(PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc)

// NumAlgo 4
expect(
getNumAlgoFromPeerDid(
'did:peer:4zQmXU3HDFaMvdiuUh7eC2hUzFxZHgaKUJpiCAkSDfRE6qSn:z2gxx5mnuv7Tuc5GxjJ3BgJ69g1ucM27iVW9xYSg9tbBjjGLKsWGSpEwqQPbCdCt4qs1aoB3HSM4eoUQALBvR52hCEq2quLwo5RzuZBjZZmuNf6FXvVCrRLQdMG52QJ285W5MUd3hK9QGCUoCNAHJprhtpvcJpoohcg5otvuHeZiffYDRWrfxKUGS83X4X7Hp2vYqdFPgBQcwoveyJcyYByu7zT3Fn8faMffCE5oP125gwsHxjkquEnCy3RMbf64NVL9bLDDk391k7W4HyScbLyh7ooJcWaDDjiFMtoi1J856cDocYtxZ7rjmWmG15pgTcBLX7o8ebKhWCrFSMWtspRuKs9VFaY366Sjce5ZxTUsBWUMCpWhQZxeZQ2h42UST5XiJJ7TV1E13a3ttWrHijPcHgX1MvvDAPGKVgU2jXSgH8bCL4mKuVjdEm4Kx5wMdDW88ougUFuLfwhXkDfP7sYAfuaCFWx286kWqkfYdopcGntPjCvDu6uonghRmxeC2qNfXkYmk3ZQJXzsxgQToixevEvfxQgFY1uuNo5288zJPQcfLHtTvgxEhHxD5wwYYeGFqgV6FTg9mZVU5xqg7w6456cLuZNPuARkfpZK78xMEUHtnr95tK91UY'
)
).toBe(PeerDidNumAlgo.ShortFormAndLongForm)
})
})
})
Loading
Loading