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

[RELEASE FOR TESTING] feat: dcql alpha #2100

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/nice-laws-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@credo-ts/openid4vc': minor
---

feat(openid4vc): add support for new dcql query syntax for oid4vp
5 changes: 5 additions & 0 deletions .changeset/tame-stingrays-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@credo-ts/core': patch
---

feat: add `claimFormat` to `Mdoc`, `MdocDeviceResponse` and `SdJwtVc` to allow for easier type narrowing
26 changes: 16 additions & 10 deletions demo-openid/src/Holder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { AskarModule } from '@credo-ts/askar'
import {
W3cJwtVerifiableCredential,
W3cJsonLdVerifiableCredential,
DifPresentationExchangeService,
Mdoc,
DidKey,
DidJwk,
Expand Down Expand Up @@ -180,19 +179,26 @@ export class Holder extends BaseAgent<ReturnType<typeof getOpenIdHolderModules>>
}

public async acceptPresentationRequest(resolvedPresentationRequest: OpenId4VcSiopResolvedAuthorizationRequest) {
const presentationExchangeService = this.agent.dependencyManager.resolve(DifPresentationExchangeService)

if (!resolvedPresentationRequest.presentationExchange) {
throw new Error('Missing presentation exchange on resolved authorization request')
if (!resolvedPresentationRequest.presentationExchange && !resolvedPresentationRequest.dcql) {
throw new Error('Missing presentation exchange or dcql on resolved authorization request')
}

const submissionResult = await this.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({
authorizationRequest: resolvedPresentationRequest.authorizationRequest,
presentationExchange: {
credentials: presentationExchangeService.selectCredentialsForRequest(
resolvedPresentationRequest.presentationExchange.credentialsForRequest
),
},
presentationExchange: resolvedPresentationRequest.presentationExchange
? {
credentials: this.agent.modules.openId4VcHolder.selectCredentialsForPresentationExchangeRequest(
resolvedPresentationRequest.presentationExchange.credentialsForRequest
),
}
: undefined,
dcql: resolvedPresentationRequest.dcql
? {
credentials: this.agent.modules.openId4VcHolder.selectCredentialsForDcqlRequest(
resolvedPresentationRequest.dcql.queryResult
),
}
: undefined,
})

return submissionResult.serverResponse
Expand Down
57 changes: 40 additions & 17 deletions demo-openid/src/HolderInquirer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
OpenId4VciResolvedCredentialOffer,
} from '@credo-ts/openid4vc'

import { DifPresentationExchangeService, Mdoc } from '@credo-ts/core'
import { Mdoc } from '@credo-ts/core'
import { preAuthorizedCodeGrantIdentifier } from '@credo-ts/openid4vc'
import console, { clear } from 'console'
import { textSync } from 'figlet'
Expand Down Expand Up @@ -217,24 +217,47 @@ export class HolderInquirer extends BaseInquirer {
const proofRequestUri = await this.inquireInput('Enter proof request: ')
this.resolvedPresentationRequest = await this.holder.resolveProofRequest(proofRequestUri)

const presentationDefinition = this.resolvedPresentationRequest?.presentationExchange?.definition
console.log(greenText(`Presentation Purpose: '${presentationDefinition?.purpose}'`))

if (this.resolvedPresentationRequest?.presentationExchange?.credentialsForRequest.areRequirementsSatisfied) {
const selectedCredentials = Object.values(
this.holder.agent.dependencyManager
.resolve(DifPresentationExchangeService)
.selectCredentialsForRequest(this.resolvedPresentationRequest.presentationExchange.credentialsForRequest)
).flatMap((e) => e)
if (this.resolvedPresentationRequest.presentationExchange) {
const presentationDefinition = this.resolvedPresentationRequest.presentationExchange.definition
console.log(
greenText(
`All requirements for creating the presentation are satisfied. The following credentials will be shared`,
true
)
greenText(`Received DIF Presentation Exchange request with purpose: '${presentationDefinition.purpose}'`)
)
selectedCredentials.forEach(this.printCredential)
} else {
console.log(redText(`No credentials available that satisfy the proof request.`))

if (this.resolvedPresentationRequest.presentationExchange.credentialsForRequest.areRequirementsSatisfied) {
const selectedCredentials = Object.values(
this.holder.agent.modules.openId4VcHolder.selectCredentialsForPresentationExchangeRequest(
this.resolvedPresentationRequest.presentationExchange.credentialsForRequest
)
).flatMap((e) => e)
console.log(
greenText(
`All requirements for creating the presentation are satisfied. The following credentials will be shared`,
true
)
)
selectedCredentials.forEach(this.printCredential)
} else {
console.log(redText(`No credentials available that satisfy the proof request.`))
}
} else if (this.resolvedPresentationRequest.dcql) {
console.log(greenText('Received DCQL request'))

if (this.resolvedPresentationRequest.dcql.queryResult.canBeSatisfied) {
const selectedCredentials = Object.values(
this.holder.agent.modules.openId4VcHolder.selectCredentialsForDcqlRequest(
this.resolvedPresentationRequest.dcql.queryResult
)
).flatMap((e) => e.credentialRecord)
console.log(
greenText(
`All requirements for creating the presentation are satisfied. The following credentials will be shared`,
true
)
)
selectedCredentials.forEach(this.printCredential)
} else {
console.log(redText(`No credentials available that satisfy the proof request.`))
}
}
}

Expand Down
116 changes: 109 additions & 7 deletions demo-openid/src/Verifier.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DifPresentationExchangeDefinitionV2 } from '@credo-ts/core'
import type { DcqlQuery, DifPresentationExchangeDefinitionV2 } from '@credo-ts/core'
import type { OpenId4VcVerifierRecord } from '@credo-ts/openid4vc'

import { AskarModule } from '@credo-ts/askar'
Expand All @@ -11,8 +11,92 @@ import { Output } from './OutputClass'

const VERIFIER_HOST = process.env.VERIFIER_HOST ?? 'http://localhost:4000'

const universityDegreeDcql = {
credential_sets: [
{
required: true,
options: [
['UniversityDegreeCredential-vc+sd-jwt'],
['UniversityDegreeCredential-jwt_vc_json-ld'],
['UniversityDegreeCredential-jwt_vc_json'],
],
},
],
credentials: [
{
id: 'UniversityDegreeCredential-vc+sd-jwt',
format: 'vc+sd-jwt',
meta: {
vct_values: ['UniversityDegree'],
},
},
{
id: 'UniversityDegreeCredential-jwt_vc_json-ld',
format: 'jwt_vc_json-ld',
claims: [
{
path: ['vc', 'type'],
values: ['UniversityDegree'],
},
],
},
{
id: 'UniversityDegreeCredential-jwt_vc_json',
format: 'jwt_vc_json',
claims: [
{
path: ['vc', 'type'],
values: ['UniversityDegree'],
},
],
},
],
} satisfies DcqlQuery

const openBadgeCredentialDcql = {
credential_sets: [
{
required: true,
options: [
['OpenBadgeCredential-vc+sd-jwt'],
['OpenBadgeCredential-jwt_vc_json-ld'],
['OpenBadgeCredential-jwt_vc_json'],
],
},
],
credentials: [
{
id: 'OpenBadgeCredential-vc+sd-jwt',
format: 'vc+sd-jwt',
meta: {
vct_values: ['OpenBadgeCredential'],
},
},
{
id: 'OpenBadgeCredential-jwt_vc_json-ld',
format: 'jwt_vc_json-ld',
claims: [
{
path: ['vc', 'type'],
values: ['OpenBadgeCredential'],
},
],
},
{
id: 'OpenBadgeCredential-jwt_vc_json',
format: 'jwt_vc_json',
claims: [
{
path: ['vc', 'type'],
values: ['OpenBadgeCredential'],
},
],
},
],
} satisfies DcqlQuery

const universityDegreePresentationDefinition = {
id: 'UniversityDegreeCredential',
id: 'UniversityDegreeCredential - DIF Presentation Exchange',
purpose: 'Present your UniversityDegreeCredential to verify your education level.',
input_descriptors: [
{
Expand All @@ -34,7 +118,7 @@ const universityDegreePresentationDefinition = {
}

const openBadgeCredentialPresentationDefinition = {
id: 'OpenBadgeCredential',
id: 'OpenBadgeCredential - DIF Presentation Exchange',
purpose: 'Provide proof of employment to confirm your employment status.',
input_descriptors: [
{
Expand All @@ -60,6 +144,11 @@ export const presentationDefinitions = [
openBadgeCredentialPresentationDefinition,
]

export const dcqls = [
{ id: 'UniversityDegreeCredential - DCQL', dcql: universityDegreeDcql },
{ id: 'OpenBadgeCredential - DCQL', dcql: openBadgeCredentialDcql },
]

export class Verifier extends BaseAgent<{ askar: AskarModule; openId4VcVerifier: OpenId4VcVerifierModule }> {
public verifierRecord!: OpenId4VcVerifierRecord

Expand Down Expand Up @@ -90,16 +179,29 @@ export class Verifier extends BaseAgent<{ askar: AskarModule; openId4VcVerifier:
}

// TODO: add method to show the received presentation submission
public async createProofRequest(presentationDefinition: DifPresentationExchangeDefinitionV2) {
public async createProofRequest({
presentationDefinition,
dcql,
}: {
presentationDefinition?: DifPresentationExchangeDefinitionV2
dcql?: DcqlQuery
}) {
const { authorizationRequest } = await this.agent.modules.openId4VcVerifier.createAuthorizationRequest({
requestSigner: {
method: 'did',
didUrl: this.verificationMethod.id,
},
verifierId: this.verifierRecord.verifierId,
presentationExchange: {
definition: presentationDefinition,
},
presentationExchange: presentationDefinition
? {
definition: presentationDefinition,
}
: undefined,
dcql: dcql
? {
query: dcql,
}
: undefined,
})

return authorizationRequest
Expand Down
12 changes: 8 additions & 4 deletions demo-openid/src/VerifierInquirer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { textSync } from 'figlet'

import { BaseInquirer } from './BaseInquirer'
import { Title, purpleText } from './OutputClass'
import { Verifier, presentationDefinitions } from './Verifier'
import { Verifier, dcqls, presentationDefinitions } from './Verifier'

export const runVerifier = async () => {
clear()
Expand Down Expand Up @@ -49,11 +49,15 @@ export class VerifierInquirer extends BaseInquirer {
}

public async createProofRequest() {
const presentationDefinitionId = await this.pickOne(presentationDefinitions.map((p) => p.id))
const presentationDefinitionId = await this.pickOne([
...presentationDefinitions.map((p) => p.id),
...dcqls.map((d) => d.id),
])
const presentationDefinition = presentationDefinitions.find((p) => p.id === presentationDefinitionId)
if (!presentationDefinition) throw new Error('No presentation definition found')
const dcql = dcqls.find((dcql) => dcql.id === presentationDefinitionId)?.dcql
if (!presentationDefinition && !dcql) throw new Error('No presentation definition found')

const proofRequest = await this.verifier.createProofRequest(presentationDefinition)
const proofRequest = await this.verifier.createProofRequest({ presentationDefinition, dcql })

console.log(purpleText(`Proof request for the presentation of an ${presentationDefinitionId}.\n'${proofRequest}'`))
}
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@
},
"resolutions": {
"@types/node": "18.18.8",
"undici": "^6.20.1"
"undici": "^6.20.1",
"@sphereon/jarm": "0.16.1-fix.173",
"@sphereon/oid4vc-common": "0.16.1-fix.173"
},
"engines": {
"node": ">=18"
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"dependencies": {
"@digitalcredentials/jsonld": "^6.0.0",
"dcql": "^0.2.11",
"@digitalcredentials/jsonld-signatures": "^9.4.0",
"@digitalcredentials/vc": "^6.0.1",
"@multiformats/base-x": "^4.0.1",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/agent/AgentModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { BasicMessagesModule } from '../modules/basic-messages'
import { CacheModule } from '../modules/cache'
import { ConnectionsModule } from '../modules/connections'
import { CredentialsModule } from '../modules/credentials'
import { DcqlModule } from '../modules/dcql'
import { DidsModule } from '../modules/dids'
import { DifPresentationExchangeModule } from '../modules/dif-presentation-exchange'
import { DiscoverFeaturesModule } from '../modules/discover-features'
Expand Down Expand Up @@ -136,6 +137,7 @@ function getDefaultAgentModules() {
w3cCredentials: () => new W3cCredentialsModule(),
cache: () => new CacheModule(),
pex: () => new DifPresentationExchangeModule(),
dcql: () => new DcqlModule(),
sdJwtVc: () => new SdJwtVcModule(),
x509: () => new X509Module(),
mdoc: () => new MdocModule(),
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/agent/__tests__/AgentModules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BasicMessagesModule } from '../../modules/basic-messages'
import { CacheModule } from '../../modules/cache'
import { ConnectionsModule } from '../../modules/connections'
import { CredentialsModule } from '../../modules/credentials'
import { DcqlModule } from '../../modules/dcql'
import { DidsModule } from '../../modules/dids'
import { DifPresentationExchangeModule } from '../../modules/dif-presentation-exchange'
import { DiscoverFeaturesModule } from '../../modules/discover-features'
Expand Down Expand Up @@ -67,6 +68,7 @@ describe('AgentModules', () => {
messagePickup: expect.any(MessagePickupModule),
basicMessages: expect.any(BasicMessagesModule),
pex: expect.any(DifPresentationExchangeModule),
dcql: expect.any(DcqlModule),
genericRecords: expect.any(GenericRecordsModule),
discovery: expect.any(DiscoverFeaturesModule),
dids: expect.any(DidsModule),
Expand Down Expand Up @@ -95,6 +97,7 @@ describe('AgentModules', () => {
messagePickup: expect.any(MessagePickupModule),
basicMessages: expect.any(BasicMessagesModule),
pex: expect.any(DifPresentationExchangeModule),
dcql: expect.any(DcqlModule),
genericRecords: expect.any(GenericRecordsModule),
discovery: expect.any(DiscoverFeaturesModule),
dids: expect.any(DidsModule),
Expand Down Expand Up @@ -126,6 +129,7 @@ describe('AgentModules', () => {
messagePickup: expect.any(MessagePickupModule),
basicMessages: expect.any(BasicMessagesModule),
pex: expect.any(DifPresentationExchangeModule),
dcql: expect.any(DcqlModule),
genericRecords: expect.any(GenericRecordsModule),
discovery: expect.any(DiscoverFeaturesModule),
dids: expect.any(DidsModule),
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export * from './modules/cache'
export * from './modules/dif-presentation-exchange'
export * from './modules/sd-jwt-vc'
export * from './modules/mdoc'
export * from './modules/dcql'
export {
JsonEncoder,
JsonTransformer,
Expand Down
Loading
Loading