Skip to content

Commit

Permalink
feat: add dcql (#28)
Browse files Browse the repository at this point in the history
* feat: dcql

Signed-off-by: Timo Glastra <[email protected]>

* typo

Signed-off-by: Timo Glastra <[email protected]>

---------

Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra authored Nov 25, 2024
1 parent 5980202 commit f4b2de2
Show file tree
Hide file tree
Showing 27 changed files with 751 additions and 1,525 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ Open three terminal windows, and then run the following:
npx ngrok http 3001
```

Copy the https url from the ngrok command and set that as the `AGENT_HOST` and `AGENT_DNS`
Copy the https url from the ngrok command and set that as the `AGENT_HOST`

```bash
cd agent
AGENT_DNS=30f9-58-136-114-148.ngrok-free.app AGENT_HOST=https://30f9-58-136-114-148.ngrok-free.app pnpm dev
AGENT_HOST=https://30f9-58-136-114-148.ngrok-free.app pnpm dev
```

```bash
Expand Down
16 changes: 9 additions & 7 deletions agent/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "agent",
"dependencies": {
"@credo-ts/askar": "0.6.0-alpha-20241119125554",
"@credo-ts/core": "0.6.0-alpha-20241119125554",
"@credo-ts/node": "0.6.0-alpha-20241119125554",
"@credo-ts/openid4vc": "0.6.0-alpha-20241119125554",
"@credo-ts/askar": "0.6.0-pr-2100-20241124131043",
"@credo-ts/core": "0.6.0-pr-2100-20241124131043",
"@credo-ts/node": "0.6.0-pr-2100-20241124131043",
"@credo-ts/openid4vc": "0.6.0-pr-2100-20241124131043",
"@hyperledger/aries-askar-nodejs": "^0.2.3",
"@animo-id/mdoc": "^0.2.38",
"@animo-id/mdoc": "^0.2.39",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.2",
Expand All @@ -27,8 +27,10 @@
"dev": "tsx watch -r dotenv/config src/server.ts dotenv_config_path=.env.development"
},
"pnpm": {
"patchedDependencies": {
"@sphereon/[email protected]": "patches/@[email protected]"
"overrides": {
"@sphereon/did-auth-siop": "https://gitpkg.vercel.app/animo/OID4VC/packages/siop-oid4vp?funke",
"@sphereon/jarm": "https://gitpkg.vercel.app/animo/OID4VC/packages/jarm?funke",
"@sphereon/oid4vc-common": "https://gitpkg.vercel.app/animo/OID4VC/packages/common?funke"
}
}
}
2 changes: 1 addition & 1 deletion agent/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ if (!process.env.P256_SEED || !process.env.AGENT_HOST || !process.env.AGENT_WALL
}

const AGENT_HOST = process.env.AGENT_HOST
const AGENT_DNS = process.env.AGENT_DNS ?? process.env.AGENT_HOST.replace('https://', '')
const AGENT_DNS = AGENT_HOST.replace('https://', '')
const AGENT_WALLET_KEY = process.env.AGENT_WALLET_KEY

const P256_SEED = process.env.P256_SEED
Expand Down
149 changes: 104 additions & 45 deletions agent/src/endpoints.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
DifPresentationExchangeService,
JsonTransformer,
KeyType,
Jwt,
MdocDeviceResponse,
RecordNotFoundError,
W3cJsonLdVerifiablePresentation,
Expand Down Expand Up @@ -80,9 +80,11 @@ apiRouter.post('/offers/create', async (request: Request, response: Response) =>

apiRouter.get('/x509', async (_, response: Response) => {
const certificate = getX509Certificate()

const instance = X509Certificate.fromEncodedCertificate(certificate)
return response.json({
certificate,
base64: instance.toString('base64'),
pem: instance.toString('pem'),
decoded: instance.toString('text'),
})
})

Expand Down Expand Up @@ -195,14 +197,20 @@ apiRouter.post('/offers/receive', async (request: Request, response: Response) =

apiRouter.get('/verifier', async (_, response: Response) => {
return response.json({
presentationRequests: verifiers.flatMap((i) =>
i.presentationRequests.map((c) => {
presentationRequests: verifiers.flatMap((i) => [
...i.presentationRequests.map((c) => {
return {
display: c.name,
display: `${i.clientMetadata.client_name} - ${c.name} - DIF PEX`,
id: c.id,
}
})
),
}),
...i.dcqlRequests.map((c) => {
return {
display: `${i.clientMetadata.client_name} - ${c.name} - DCQL`,
id: c.id,
}
}),
]),
})
})

Expand All @@ -225,13 +233,17 @@ apiRouter.post('/requests/create', async (request: Request, response: Response)
})
}

const verifierId = verifiers.find((a) => a.presentationRequests.find((r) => r.id === definition.id))?.verifierId
const verifierId = verifiers.find(
(a) =>
a.presentationRequests.find((r) => r.id === definition.id) ?? a.dcqlRequests.find((r) => r.id === definition.id)
)?.verifierId
if (!verifierId) {
return response.status(404).json({
error: 'Verifier not found',
})
}
const verifier = await getVerifier(verifierId)
console.log('Requesting definition', JSON.stringify(definition, null, 2))

const { authorizationRequest, verificationSession } =
await agent.modules.openId4VcVerifier.createAuthorizationRequest({
Expand All @@ -242,17 +254,37 @@ apiRouter.post('/requests/create', async (request: Request, response: Response)
// FIXME: remove issuer param from credo as we can infer it from the url
issuer: `${AGENT_HOST}/siop/${verifier.verifierId}/authorize`,
},
presentationExchange: {
definition,
},
presentationExchange:
'input_descriptors' in definition
? {
definition,
}
: undefined,
dcql:
'credentials' in definition
? {
query: definition,
}
: undefined,
responseMode: createPresentationRequestBody.responseMode,
})

console.log(authorizationRequest)

const authorizationRequestJwt = Jwt.fromSerializedJwt(verificationSession.authorizationRequestJwt)
const dcqlQuery = authorizationRequestJwt.payload.additionalClaims.dcql_query
const presentationDefinition = authorizationRequestJwt.payload.additionalClaims.presentation_definition

return response.json({
authorizationRequestUri: authorizationRequest.replace('openid4vp://', createPresentationRequestBody.requestScheme),
verificationSessionId: verificationSession.id,
responseStatus: verificationSession.state,
dcqlQuery: dcqlQuery ? JSON.parse(dcqlQuery as string) : undefined,
definition: presentationDefinition,
authorizationRequest: {
payload: authorizationRequestJwt.payload.toJson(),
header: authorizationRequestJwt.header,
},
})
})

Expand All @@ -266,70 +298,97 @@ apiRouter.get('/requests/:verificationSessionId', async (request, response) => {
try {
const verificationSession = await agent.modules.openId4VcVerifier.getVerificationSessionById(verificationSessionId)

const authorizationRequestJwt = Jwt.fromSerializedJwt(verificationSession.authorizationRequestJwt)
const authorizationRequest = {
payload: authorizationRequestJwt.payload.toJson(),
header: authorizationRequestJwt.header,
}
const dcqlQuery = authorizationRequestJwt.payload.additionalClaims.dcql_query
const presentationDefinition = authorizationRequestJwt.payload.additionalClaims.presentation_definition

if (verificationSession.state === OpenId4VcVerificationSessionState.ResponseVerified) {
const verified = await agent.modules.openId4VcVerifier.getVerifiedAuthorizationResponse(verificationSessionId)

console.log(verified.presentationExchange?.presentations)
console.log(verified.dcql?.presentationResult)

const presentations = await Promise.all(
verified.presentationExchange?.presentations.map(async (presentation) => {
if (presentation instanceof W3cJsonLdVerifiablePresentation) {
return {
pretty: presentation.toJson(),
encoded: presentation.toJson(),
(verified.presentationExchange?.presentations ?? Object.values(verified.dcql?.presentation ?? {})).map(
async (presentation) => {
if (presentation instanceof W3cJsonLdVerifiablePresentation) {
return {
pretty: presentation.toJson(),
encoded: presentation.toJson(),
}
}
}

if (presentation instanceof W3cJwtVerifiablePresentation) {
return {
pretty: JsonTransformer.toJSON(presentation.presentation),
encoded: presentation.serializedJwt,
if (presentation instanceof W3cJwtVerifiablePresentation) {
return {
pretty: JsonTransformer.toJSON(presentation.presentation),
encoded: presentation.serializedJwt,
}
}
}

if (presentation instanceof MdocDeviceResponse) {
return {
pretty: JsonTransformer.toJSON({
documents: presentation.documents.map((doc) => ({
doctype: doc.docType,
alg: doc.alg,
base64Url: doc.base64Url,
validityInfo: doc.validityInfo,
deviceSignedNamespaces: doc.deviceSignedNamespaces,
issuerSignedNamespaces: doc.issuerSignedNamespaces,
})),
}),
encoded: presentation.base64Url,
if (presentation instanceof MdocDeviceResponse) {
return {
pretty: JsonTransformer.toJSON({
documents: presentation.documents.map((doc) => ({
doctype: doc.docType,
alg: doc.alg,
base64Url: doc.base64Url,
validityInfo: doc.validityInfo,
deviceSignedNamespaces: doc.deviceSignedNamespaces,
issuerSignedNamespaces: doc.issuerSignedNamespaces,
})),
}),
encoded: presentation.base64Url,
}
}
}

return {
pretty: {
...presentation,
compact: undefined,
},
encoded: presentation.compact,
return {
pretty: {
...presentation,
compact: undefined,
},
encoded: presentation.compact,
}
}
}) ?? []
) ?? []
)

const dcqlSubmission = verified.dcql
? Object.keys(verified.dcql.presentation).map((key, index) => ({
queryCredentialId: key,
presentationIndex: index,
}))
: undefined

console.log('presentations', presentations)

return response.json({
verificationSessionId: verificationSession.id,
responseStatus: verificationSession.state,
error: verificationSession.errorMessage,
authorizationRequest,

presentations: presentations,

submission: verified.presentationExchange?.submission,
definition: verified.presentationExchange?.definition,

dcqlQuery: dcqlQuery ? JSON.parse(dcqlQuery as string) : undefined,
dcqlSubmission: verified.dcql
? { ...verified.dcql.presentationResult, vpTokenMapping: dcqlSubmission }
: undefined,
})
}

return response.json({
verificationSessionId: verificationSession.id,
responseStatus: verificationSession.state,
error: verificationSession.errorMessage,
authorizationRequest,
definition: presentationDefinition,
dcqlQuery: dcqlQuery ? JSON.parse(dcqlQuery as string) : undefined,
})
} catch (error) {
if (error instanceof RecordNotFoundError) {
Expand Down
Loading

0 comments on commit f4b2de2

Please sign in to comment.