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: credential protocols v3 #1560

Draft
wants to merge 26 commits into
base: feat/didcomm-v2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
250db50
feat: Basic Messages V2 for DIDComm V2
genaris Aug 17, 2023
191ad5b
feat: initial work on IC V3 (wip)
genaris Aug 17, 2023
39d5323
test: adapt to V2 Basic Messages and some DIDComm V2 stuff
genaris Aug 19, 2023
3b0795d
fix: expose V1BasicMessage as BasicMessage
genaris Aug 21, 2023
919dc3b
fix: v3 handlers and types
genaris Aug 22, 2023
35f75f4
feat: generalize getOutboundMessageContext
genaris Aug 22, 2023
d7a5fa5
feat: use v3 in demo
genaris Aug 22, 2023
dba690f
Merge branch 'feat/basic-messages-v2' into feat/issue-credentials-v3
genaris Aug 22, 2023
ab0be37
feat: set from/to in getOutboundContext
genaris Aug 22, 2023
35c15c4
feat: transform V1/V2 attachments
genaris Aug 22, 2023
3b850d9
fix: basic message types
genaris Aug 22, 2023
1b493fb
fix: use DIDComm v2 for ICV3 Ack message
genaris Aug 23, 2023
e1c3629
feat: generalize getOutboundMessageContext
genaris Aug 22, 2023
30eaf5b
fix: basic message types
genaris Aug 22, 2023
821ea02
feat: set from/to in getOutboundContext
genaris Aug 22, 2023
0cfebbd
Merge branch 'feat/basic-messages-v2' into feat/issue-credentials-v3
genaris Aug 23, 2023
318e5a0
fix: revert changes in BasicMessageStateChanged event
genaris Aug 28, 2023
2bcaf3f
fix: remove unneeded casting
genaris Aug 28, 2023
8cb618b
fix: remove unneeded cast
genaris Aug 28, 2023
da9a00e
feat: initial Present Proof V3 implementation
genaris Aug 28, 2023
f12938d
fix: body and attachments in ICV3
genaris Aug 29, 2023
4d15497
fix: OOB in outbound message context
genaris Aug 29, 2023
1329192
test: fix module tests
genaris Aug 29, 2023
4b211d9
Merge branch 'feat/basic-messages-v2' into feat/credential-protocols-v3
genaris Aug 29, 2023
323032e
fix: OOB in outbound message context
genaris Aug 29, 2023
793fd1a
Merge branch 'feat/basic-messages-v2' into feat/credential-protocols-v3
genaris Aug 29, 2023
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
8 changes: 8 additions & 0 deletions demo/src/BaseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ import {
DidsModule,
V2ProofProtocol,
V2CredentialProtocol,
V3CredentialProtocol,
ProofsModule,
AutoAcceptProof,
AutoAcceptCredential,
CredentialsModule,
Agent,
HttpOutboundTransport,
V3ProofProtocol,
} from '@aries-framework/core'
import { IndySdkAnonCredsRegistry, IndySdkModule, IndySdkSovDidResolver } from '@aries-framework/indy-sdk'
import { IndyVdrIndyDidResolver, IndyVdrAnonCredsRegistry, IndyVdrModule } from '@aries-framework/indy-vdr'
Expand Down Expand Up @@ -122,6 +124,9 @@ function getAskarAnonCredsIndyModules() {
new V2CredentialProtocol({
credentialFormats: [legacyIndyCredentialFormatService, new AnonCredsCredentialFormatService()],
}),
new V3CredentialProtocol({
credentialFormats: [legacyIndyCredentialFormatService, new AnonCredsCredentialFormatService()],
}),
],
}),
proofs: new ProofsModule({
Expand All @@ -133,6 +138,9 @@ function getAskarAnonCredsIndyModules() {
new V2ProofProtocol({
proofFormats: [legacyIndyProofFormatService, new AnonCredsProofFormatService()],
}),
new V3ProofProtocol({
proofFormats: [legacyIndyProofFormatService, new AnonCredsProofFormatService()],
}),
],
}),
anoncreds: new AnonCredsModule({
Expand Down
4 changes: 2 additions & 2 deletions demo/src/Faber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class Faber extends BaseAgent {

await this.agent.credentials.offerCredential({
connectionId: connectionRecord.id,
protocolVersion: 'v2',
protocolVersion: connectionRecord.isDidCommV1Connection ? 'v2' : 'v3',
credentialFormats: {
anoncreds: {
attributes: [
Expand Down Expand Up @@ -256,7 +256,7 @@ export class Faber extends BaseAgent {
await this.printProofFlow(greenText('\nRequesting proof...\n', false))

await this.agent.proofs.requestProof({
protocolVersion: 'v2',
protocolVersion: connectionRecord.isDidCommV1Connection ? 'v2' : 'v3',
connectionId: connectionRecord.id,
proofFormats: {
anoncreds: {
Expand Down
19 changes: 13 additions & 6 deletions demo/src/Listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { Faber } from './Faber'
import type { FaberInquirer } from './FaberInquirer'
import type {
Agent,
BasicMessageStateChangedEvent,
CredentialExchangeRecord,
CredentialStateChangedEvent,
TrustPingReceivedEvent,
Expand All @@ -13,12 +12,15 @@ import type {
V2TrustPingResponseReceivedEvent,
ProofExchangeRecord,
ProofStateChangedEvent,
AgentMessageProcessedEvent,
AgentBaseMessage,
} from '@aries-framework/core'
import type BottomBar from 'inquirer/lib/ui/bottom-bar'

import {
BasicMessageEventTypes,
BasicMessageRole,
V1BasicMessage,
V2BasicMessage,
AgentEventTypes,
CredentialEventTypes,
CredentialState,
ProofEventTypes,
Expand Down Expand Up @@ -76,9 +78,14 @@ export class Listener {
}

public messageListener(agent: Agent, name: string) {
agent.events.on(BasicMessageEventTypes.BasicMessageStateChanged, async (event: BasicMessageStateChangedEvent) => {
if (event.payload.basicMessageRecord.role === BasicMessageRole.Receiver) {
this.ui.updateBottomBar(purpleText(`\n${name} received a message: ${event.payload.message.content}\n`))
const isBasicMessage = (message: AgentBaseMessage): message is V1BasicMessage | V2BasicMessage =>
[V1BasicMessage.type.messageTypeUri, V2BasicMessage.type.messageTypeUri].includes(message.type)

agent.events.on(AgentEventTypes.AgentMessageProcessed, async (event: AgentMessageProcessedEvent) => {
const message = event.payload.message

if (isBasicMessage(message)) {
this.ui.updateBottomBar(purpleText(`\n${name} received a message: ${message.content}\n`))
}
})
}
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/agent/BaseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AgentConfig } from './AgentConfig'
import type { AgentApi, CustomOrDefaultApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules } from './AgentModules'
import type { TransportSession } from './TransportService'
import type { Logger } from '../logger'
import type { BasicMessagesModule } from '../modules/basic-messages'
import type { CredentialsModule } from '../modules/credentials'
import type { MessagePickupModule } from '../modules/message-pìckup'
import type { ProofsModule } from '../modules/proofs'
Expand Down Expand Up @@ -51,7 +52,7 @@ export abstract class BaseAgent<AgentModules extends ModulesMap = EmptyModuleMap
public readonly mediator: MediatorApi
public readonly mediationRecipient: MediationRecipientApi
public readonly messagePickup: CustomOrDefaultApi<AgentModules['messagePickup'], MessagePickupModule>
public readonly basicMessages: BasicMessagesApi
public readonly basicMessages: CustomOrDefaultApi<AgentModules['basicMessages'], BasicMessagesModule>
public readonly genericRecords: GenericRecordsApi
public readonly discovery: DiscoverFeaturesApi
public readonly dids: DidsApi
Expand Down Expand Up @@ -99,7 +100,10 @@ export abstract class BaseAgent<AgentModules extends ModulesMap = EmptyModuleMap
AgentModules['messagePickup'],
MessagePickupModule
>
this.basicMessages = this.dependencyManager.resolve(BasicMessagesApi)
this.basicMessages = this.dependencyManager.resolve(BasicMessagesApi) as CustomOrDefaultApi<
AgentModules['basicMessages'],
BasicMessagesModule
>
this.genericRecords = this.dependencyManager.resolve(GenericRecordsApi)
this.discovery = this.dependencyManager.resolve(DiscoverFeaturesApi)
this.dids = this.dependencyManager.resolve(DidsApi)
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/agent/__tests__/Agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { injectable } from 'tsyringe'
import { getIndySdkModules } from '../../../../indy-sdk/tests/setupIndySdkModule'
import { getAgentOptions } from '../../../tests/helpers'
import { InjectionSymbols } from '../../constants'
import { BasicMessageRepository, BasicMessageService } from '../../modules/basic-messages'
import { BasicMessageRepository } from '../../modules/basic-messages'
import { BasicMessagesApi } from '../../modules/basic-messages/BasicMessagesApi'
import { ConnectionsApi } from '../../modules/connections/ConnectionsApi'
import { V1TrustPingService } from '../../modules/connections/protocols/trust-ping/v1/V1TrustPingService'
Expand Down Expand Up @@ -167,7 +167,6 @@ describe('Agent', () => {
expect(container.resolve(CredentialRepository)).toBeInstanceOf(CredentialRepository)

expect(container.resolve(BasicMessagesApi)).toBeInstanceOf(BasicMessagesApi)
expect(container.resolve(BasicMessageService)).toBeInstanceOf(BasicMessageService)
expect(container.resolve(BasicMessageRepository)).toBeInstanceOf(BasicMessageRepository)

expect(container.resolve(MediatorApi)).toBeInstanceOf(MediatorApi)
Expand Down Expand Up @@ -205,7 +204,6 @@ describe('Agent', () => {
expect(container.resolve(CredentialRepository)).toBe(container.resolve(CredentialRepository))

expect(container.resolve(BasicMessagesApi)).toBe(container.resolve(BasicMessagesApi))
expect(container.resolve(BasicMessageService)).toBe(container.resolve(BasicMessageService))
expect(container.resolve(BasicMessageRepository)).toBe(container.resolve(BasicMessageRepository))

expect(container.resolve(MediatorApi)).toBe(container.resolve(MediatorApi))
Expand Down Expand Up @@ -242,10 +240,13 @@ describe('Agent', () => {
expect(protocols).toEqual(
expect.arrayContaining([
'https://didcomm.org/basicmessage/1.0',
'https://didcomm.org/basicmessage/2.0',
'https://didcomm.org/connections/1.0',
'https://didcomm.org/coordinate-mediation/1.0',
'https://didcomm.org/issue-credential/2.0',
'https://didcomm.org/issue-credential/3.0',
'https://didcomm.org/present-proof/2.0',
'https://didcomm.org/present-proof/3.0',
'https://didcomm.org/didexchange/1.0',
'https://didcomm.org/discover-features/1.0',
'https://didcomm.org/discover-features/2.0',
Expand All @@ -256,6 +257,6 @@ describe('Agent', () => {
'https://didcomm.org/revocation_notification/2.0',
])
)
expect(protocols.length).toEqual(13)
expect(protocols.length).toEqual(16)
})
})
28 changes: 24 additions & 4 deletions packages/core/src/agent/getOutboundMessageContext.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { AgentBaseMessage } from './AgentBaseMessage'
import type { AgentContext } from './context'
import type { DidCommV1Message } from '../didcomm/versions/v1'
import type { ConnectionRecord, Routing } from '../modules/connections'
import type { ResolvedDidCommService } from '../modules/didcomm'
import type { OutOfBandRecord } from '../modules/oob'
import type { BaseRecordAny } from '../storage/BaseRecord'

import { Key } from '../crypto'
import { ServiceDecorator } from '../decorators/service/ServiceDecorator'
import { DidCommV1Message, DidCommV2Message } from '../didcomm'
import { AriesFrameworkError } from '../error'
import { OutOfBandService, OutOfBandRole, OutOfBandRepository } from '../modules/oob'
import { OutOfBandRecordMetadataKeys } from '../modules/oob/repository/outOfBandRecordMetadataTypes'
Expand Down Expand Up @@ -37,9 +38,9 @@ export async function getOutboundMessageContext(
}: {
connectionRecord?: ConnectionRecord
associatedRecord?: BaseRecordAny
message: DidCommV1Message
lastReceivedMessage?: DidCommV1Message
lastSentMessage?: DidCommV1Message
message: AgentBaseMessage
lastReceivedMessage?: AgentBaseMessage
lastSentMessage?: AgentBaseMessage
}
) {
// TODO: even if using a connection record, we should check if there's an oob record associated and this
Expand All @@ -48,6 +49,17 @@ export async function getOutboundMessageContext(
agentContext.config.logger.debug(
`Creating outbound message context for message ${message.id} with connection ${connectionRecord.id}`
)

// Attach 'from' and 'to' fields according to connection record (unless they are previously defined)
if (message instanceof DidCommV2Message) {
message.from = message.from ?? connectionRecord.did
const recipients = message.to ?? (connectionRecord.theirDid ? [connectionRecord.theirDid] : undefined)
if (!recipients) {
throw new AriesFrameworkError('Cannot find recipient did for message')
}
message.to = recipients
}

return new OutboundMessageContext(message, {
agentContext,
associatedRecord,
Expand All @@ -67,6 +79,14 @@ export async function getOutboundMessageContext(
)
}

if (
!(message instanceof DidCommV1Message) ||
(lastReceivedMessage !== undefined && !(lastReceivedMessage instanceof DidCommV1Message)) ||
(lastSentMessage !== undefined && !(lastSentMessage instanceof DidCommV1Message))
) {
throw new AriesFrameworkError('No connection record associated with DIDComm V2 messages exchange')
}

// Connectionless
return getConnectionlessOutboundMessageContext(agentContext, {
message,
Expand Down
40 changes: 36 additions & 4 deletions packages/core/src/decorators/attachment/v2/V2Attachment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { JwsDetachedFormat, JwsFlattenedDetachedFormat } from '../../../crypto/JwsTypes'

import { Expose, Type } from 'class-transformer'
import { IsBase64, IsInstance, IsMimeType, IsOptional, IsString, ValidateNested } from 'class-validator'
import { IsBase64, IsDate, IsInstance, IsInt, IsMimeType, IsOptional, IsString, ValidateNested } from 'class-validator'

import { Jws } from '../../../crypto/JwsTypes'
import { AriesFrameworkError } from '../../../error'
import { JsonEncoder } from '../../../utils/JsonEncoder'
import { uuid } from '../../../utils/uuid'
Expand All @@ -12,6 +13,8 @@ export interface V2AttachmentOptions {
id?: string
description?: string
filename?: string
format?: string
lastmodTime?: Date
mediaType?: string
byteCount?: number
data: V2AttachmentData
Expand All @@ -21,7 +24,8 @@ export interface V2AttachmentDataOptions {
base64?: string
json?: Record<string, unknown>
links?: string[]
jws?: Jws
jws?: JwsDetachedFormat | JwsFlattenedDetachedFormat
hash?: string
}

/**
Expand Down Expand Up @@ -52,14 +56,22 @@ export class V2AttachmentData {
* A JSON Web Signature over the content of the attachment. Optional.
*/
@IsOptional()
public jws?: Jws
public jws?: JwsDetachedFormat | JwsFlattenedDetachedFormat

/**
* The hash of the content encoded in multi-hash format. Used as an integrity check for the attachment, and MUST be used if the data is referenced via the links data attribute.
*/
@IsOptional()
@IsString()
public hash?: string

public constructor(options: V2AttachmentDataOptions) {
if (options) {
this.base64 = options.base64
this.json = options.json
this.links = options.links
this.jws = options.jws
this.hash = options.hash
}
}
}
Expand All @@ -73,7 +85,10 @@ export class V2Attachment {
if (options) {
this.id = options.id ?? uuid()
this.description = options.description
this.byteCount = options.byteCount
this.filename = options.filename
this.format = options.format
this.lastmodTime = options.lastmodTime
this.mediaType = options.mediaType
this.data = options.data
}
Expand Down Expand Up @@ -110,6 +125,23 @@ export class V2Attachment {
@IsMimeType()
public mediaType?: string

/**
* A hint about when the content in this attachment was last modified.
*/
@Expose({ name: 'lastmod_time' })
@Type(() => Date)
@IsOptional()
@IsDate()
public lastmodTime?: Date

/**
* Optional, and mostly relevant when content is included by reference instead of by value. Lets the receiver guess how expensive it will be, in time, bandwidth, and storage, to fully fetch the attachment.
*/
@Expose({ name: 'byte_count' })
@IsOptional()
@IsInt()
public byteCount?: number

@Type(() => V2AttachmentData)
@ValidateNested()
@IsInstance(V2AttachmentData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export function ThreadDecorated<T extends DidComV1BaseMessageConstructor>(Base:
return this.thread?.threadId ?? this.id
}

public get parentThreadId(): string | undefined {
return this.thread?.parentThreadId
}

public setThread(options: Partial<ThreadDecorator>) {
this.thread = new ThreadDecorator(options)
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/didcomm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Constructor } from '../utils/mixins'
export * from './versions/v1'
export * from './versions/v2'
export * from './types'
export * from './transformers'
export * from './helpers'
export * from './JweEnvelope'

Expand Down
39 changes: 39 additions & 0 deletions packages/core/src/didcomm/transformers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Attachment, AttachmentData, V2Attachment, V2AttachmentData } from '../decorators/attachment'

export function toV2Attachment(v1Attachment: Attachment): V2Attachment {
const { id, description, byteCount, filename, lastmodTime, mimeType, data } = v1Attachment
return new V2Attachment({
id,
description,
byteCount,
filename,
lastmodTime,
mediaType: mimeType,
data: new V2AttachmentData({
base64: data.base64,
json: data.json,
jws: data.jws,
links: data.links,
hash: data.sha256,
}),
})
}

export function toV1Attachment(v2Attachment: V2Attachment): Attachment {
const { id, description, byteCount, filename, lastmodTime, mediaType, data } = v2Attachment
return new Attachment({
id,
description,
byteCount,
filename,
lastmodTime,
mimeType: mediaType,
data: new AttachmentData({
base64: data.base64,
json: data.json,
jws: data.jws,
links: data.links,
sha256: data.hash,
}),
})
}
Loading