diff --git a/lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts b/lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts index 5e24d0d..0a01366 100644 --- a/lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts +++ b/lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts @@ -10,6 +10,7 @@ import { WrappedVerifiableCredential, } from '@sphereon/ssi-types'; +import { ClaimValue } from '../../../test/types'; import { Status } from '../../ConstraintUtils'; import { IInternalPresentationDefinition, InputDescriptorWithIndex, PathComponent } from '../../types'; import PexMessages from '../../types/Messages'; @@ -139,15 +140,43 @@ export class LimitDisclosureEvaluationHandler extends AbstractEvaluationHandler // Can be nested array / object const presentationFrame: SdJwtPresentationFrame = {}; + const processNestedObject = (obj: ClaimValue, currentPath: PathComponent[], basePath: PathComponent[]) => { + if (obj === null || typeof obj !== 'object') { + // For literal values, set the path to true in the presentation frame + JsonPathUtils.setValue(presentationFrame, currentPath, true); + return; + } + + // For arrays, process each element + if (Array.isArray(obj)) { + obj.forEach((item, index) => { + processNestedObject(item, [...currentPath, index], basePath); + }); + return; + } + + // For objects, process each child property + Object.entries(obj).forEach(([key, value]) => { + processNestedObject(value, [...currentPath, key], basePath); + }); + }; + for (const { inputDescriptor, inputDescriptorIndex } of inputDescriptors) { for (const field of inputDescriptor.constraints?.fields ?? []) { if (field.path) { const inputField = JsonPathUtils.extractInputField(vc.decodedPayload, field.path); - // We set the value to true at the path in the presentation frame, if (inputField.length > 0) { const selectedField = inputField[0]; - JsonPathUtils.setValue(presentationFrame, selectedField.path, true); + const fieldValue = JsonPathUtils.getValue(vc.decodedPayload, selectedField.path); + + if (fieldValue !== null && typeof fieldValue === 'object') { + // For objects, recursively process all nested fields + processNestedObject(fieldValue, selectedField.path, selectedField.path); + } else { + // For literal values, just set the path to true + JsonPathUtils.setValue(presentationFrame, selectedField.path, true); + } } else { this.createMandatoryFieldNotFoundResult(inputDescriptorIndex, vcIndex, field.path); return undefined; diff --git a/lib/utils/jsonPathUtils.ts b/lib/utils/jsonPathUtils.ts index bcd2f19..4ced2a4 100644 --- a/lib/utils/jsonPathUtils.ts +++ b/lib/utils/jsonPathUtils.ts @@ -78,6 +78,15 @@ export class JsonPathUtils { return obj; } + public static getValue(obj: InputFieldType, path: string | PathComponent[]): T | undefined { + try { + const stringPath = typeof path === 'string' ? path : jp.stringify(path); + return jp.value(obj, stringPath) as T; + } catch (error) { + return undefined; + } + } + private static copyResultPathToDestinationDefinition(pathDetails: (string | number)[], pd: IPresentationDefinition, newPropertyName: string) { // eslint-disable-next-line @typescript-eslint/no-explicit-any let objectCursor: any = pd; diff --git a/lib/utils/sdJwt.ts b/lib/utils/sdJwt.ts index 9bcea98..84d3249 100644 --- a/lib/utils/sdJwt.ts +++ b/lib/utils/sdJwt.ts @@ -28,7 +28,7 @@ export function applySdJwtLimitDisclosure( sdJwtDecodedVerifiableCredential: SdJwtDecodedVerifiableCredential, presentationFrame: SdJwtPresentationFrame, ) { - const SerializedDisclosures = sdJwtDecodedVerifiableCredential.disclosures.map((d) => ({ + const serializedDisclosures = sdJwtDecodedVerifiableCredential.disclosures.map((d) => ({ digest: d.digest, encoded: d.encoded, salt: d.decoded[0], @@ -39,7 +39,7 @@ export function applySdJwtLimitDisclosure( const requiredDisclosures = selectDisclosures( ObjectUtils.cloneDeep(sdJwtDecodedVerifiableCredential.signedPayload), // Map to sd-jwt disclosure format - SerializedDisclosures, + serializedDisclosures, presentationFrame as PresentationFrame>, ); diff --git a/package.json b/package.json index 4d1f79a..b675a91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sphereon/pex", - "version": "5.0.0-unstable.18", + "version": "5.0.0-unstable.19", "description": "A Typescript implementation of the v1 and v2 DIF Presentation Exchange specification", "main": "dist/main/index.js", "module": "dist/module/index.js", diff --git a/test/dif_pe_examples/pdV2/pd-sd-jwt-vp-funke.json b/test/dif_pe_examples/pdV2/pd-sd-jwt-vp-funke.json new file mode 100644 index 0000000..6c2edbd --- /dev/null +++ b/test/dif_pe_examples/pdV2/pd-sd-jwt-vp-funke.json @@ -0,0 +1,54 @@ +{ + "presentation_definition": { + "id": "pid-sdjwt", + "name": "Identity verification SD-JWT", + "purpose": "We need to verify your identity information.", + "format": { + "vc+sd-jwt": { + "sd-jwt_alg_values": [ + "ES256", + "ES384", + "ES512", + "EdDSA" + ] + } + }, + "input_descriptors": [ + { + "id": "6aa0ac61-6535-46c2-924d-65fccfe65f4f", + "constraints": { + "limit_disclosure": "required", + "fields": [ + { + "path": [ + "$.place_of_birth" + ] + }, + { + "path": [ + "$.given_name" + ] + }, + { + "path": [ + "$.family_name" + ] + }, + { + "path": [ + "$.address" + ] + }, + { + "path": [ + "$.age_equal_or_over.18" + ] + } + ] + }, + "name": "Identity", + "purpose": "We need your identity information." + } + ] + } +} diff --git a/test/dif_pe_examples/vc/vc-funke-pid-sd.jwt b/test/dif_pe_examples/vc/vc-funke-pid-sd.jwt new file mode 100644 index 0000000..bf5570d --- /dev/null +++ b/test/dif_pe_examples/vc/vc-funke-pid-sd.jwt @@ -0,0 +1 @@ +eyJ4NWMiOlsiTUlJQ2REQ0NBaHVnQXdJQkFnSUJBakFLQmdncWhrak9QUVFEQWpDQmlERUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneEVUQVBCZ05WQkFzTUNGUWdRMU1nU1VSRk1UWXdOQVlEVlFRRERDMVRVRkpKVGtRZ1JuVnVhMlVnUlZWRVNTQlhZV3hzWlhRZ1VISnZkRzkwZVhCbElFbHpjM1ZwYm1jZ1EwRXdIaGNOTWpRd05UTXhNRGd4TXpFM1doY05NalV3TnpBMU1EZ3hNekUzV2pCc01Rc3dDUVlEVlFRR0V3SkVSVEVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hNakF3QmdOVkJBTU1LVk5RVWtsT1JDQkdkVzVyWlNCRlZVUkpJRmRoYkd4bGRDQlFjbTkwYjNSNWNHVWdTWE56ZFdWeU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU9GQnE0WU1LZzR3NWZUaWZzeXR3QnVKZi83RTdWaFJQWGlObTUyUzNxMUVUSWdCZFh5REsza1Z4R3hnZUhQaXZMUDN1dU12UzZpREVjN3FNeG12ZHVLT0JrRENCalRBZEJnTlZIUTRFRmdRVWlQaENrTEVyRFhQTFcyL0owV1ZlZ2h5dyttSXdEQVlEVlIwVEFRSC9CQUl3QURBT0JnTlZIUThCQWY4RUJBTUNCNEF3TFFZRFZSMFJCQ1l3SklJaVpHVnRieTV3YVdRdGFYTnpkV1Z5TG1KMWJtUmxjMlJ5ZFdOclpYSmxhUzVrWlRBZkJnTlZIU01FR0RBV2dCVFVWaGpBaVRqb0RsaUVHTWwyWXIrcnU4V1F2akFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUFiZjVUemtjUXpoZldvSW95aTFWTjdkOEk5QnNGS20xTVdsdVJwaDJieUdRSWdLWWtkck5mMnhYUGpWU2JqVy9VLzVTNXZBRUM1WHhjT2FudXNPQnJvQmJVPSIsIk1JSUNlVENDQWlDZ0F3SUJBZ0lVQjVFOVFWWnRtVVljRHRDaktCL0gzVlF2NzJnd0NnWUlLb1pJemowRUF3SXdnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUI0WERUSTBNRFV6TVRBMk5EZ3dPVm9YRFRNME1EVXlPVEEyTkRnd09Wb3dnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFWUd6ZHdGRG5jNytLbjVpYkF2Q09NOGtlNzdWUXhxZk1jd1pMOElhSUErV0NST2NDZm1ZL2dpSDkycU1ydTVwL2t5T2l2RTBSQy9JYmRNT052RG9VeWFObU1HUXdIUVlEVlIwT0JCWUVGTlJXR01DSk9PZ09XSVFZeVhaaXY2dTd4WkMrTUI4R0ExVWRJd1FZTUJhQUZOUldHTUNKT09nT1dJUVl5WFppdjZ1N3haQytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnR0dNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJR0VtN3drWktIdC9hdGI0TWRGblhXNnlybndNVVQydTEzNmdkdGwxMFk2aEFpQnVURnF2Vll0aDFyYnh6Q1AweFdaSG1RSzlrVnl4bjhHUGZYMjdFSXp6c3c9PSJdLCJraWQiOiJNSUdVTUlHT3BJR0xNSUdJTVFzd0NRWURWUVFHRXdKRVJURVBNQTBHQTFVRUJ3d0dRbVZ5YkdsdU1SMHdHd1lEVlFRS0RCUkNkVzVrWlhOa2NuVmphMlZ5WldrZ1IyMWlTREVSTUE4R0ExVUVDd3dJVkNCRFV5QkpSRVV4TmpBMEJnTlZCQU1NTFZOUVVrbE9SQ0JHZFc1clpTQkZWVVJKSUZkaGJHeGxkQ0JRY205MGIzUjVjR1VnU1hOemRXbHVaeUJEUVFJQkFnPT0iLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiNkJQYWczZ0pwZ1JLLU1DdXlLSXZ5Q3B2RHdrdFZoZG1SMm9GWnF0UDJQVSJdfSwiX3NkIjpbIi1tdXhPS01BUHREUWl0WW1YcWJxZVhSTmc4ckJBbHNmZUNZYlhVRWY5WTQiLCJIRHdYOFVWREF0RVdvRUlIbER1eVFMQURoWXRlUHVPTFJGM2VnaHhLcmJRIiwiZENnbVpkTWNMalJtM0d2Rm0wb211NjcyMVlYbktiZ05lLXhDN25heHoybyIsImc1akowdktRRFJqaTAxNm5vcVc3Z293cTlXVUxpMU9JaWlLUVJpZXlZbjQiLCJzY1l6aVFTU2JJeUxpM0lUazd1Q0dFc3Jua3BsODZpZGFhRUFZN3IycUNnIiwieHJmTmRLcXF2TDU5SjZiQ1BicjctRXZuS0JZVm9Hc3FkVk5MZlhhMDMzNCIsInlJbWtEYm9SUE5sUHBBWDJFTEM1SURJNlpNbDdOVXloblRQXzJTaUItR00iXSwiYWRkcmVzcyI6eyJfc2QiOlsiTlc3S3lQY2hFVzBfV1hwWi1yNHlia0dRNjBOLVlYdktuYUFmZks0VTFHTSIsIllsT0dvT1NielJhVmhvQUFlTjRTTzBSZ0p1THNPcHdPSG5jLVBsOEtyWlEiLCJhOFA4SHk0QjRPV19TYm1JUlFhMzhfOUh5bXU5ZFN5TWZZekt4S1d1SGJVIiwiY19hMXhBLUVwS3M5X0ZaMF9uUXJJa3czR2FwWlpBNjVQOTJFUHFtUkJxSSJdfSwiaXNzdWluZ19jb3VudHJ5IjoiREUiLCJ2Y3QiOiJodHRwczovL2V4YW1wbGUuYm1pLmJ1bmQuZGUvY3JlZGVudGlhbC9waWQvMS4wIiwiaXNzdWluZ19hdXRob3JpdHkiOiJERSIsIl9zZF9hbGciOiJzaGEtMjU2IiwiaXNzIjoiaHR0cHM6Ly9kZW1vLnBpZC1pc3N1ZXIuYnVuZGVzZHJ1Y2tlcmVpLmRlL2MiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJ0OG9KZUQzRmY2VWV2bThVZDJUTG80RGtfOHRtUTJOQkdrQ3Z3eENDOHJBIiwieCI6InFCdWVJWDFrcnVlTURMWmtRd0xEcElTWTBkWURkNEIzVm12Z0NxcXVlQzQiLCJ5IjoiWGYwQzl4a1NJalFpUmU1VV9JVnFWb3RQU3cydG4xLU16VnhQNnZWeVBsNCIsImFsZyI6IkVTMjU2In19LCJleHAiOjE3MzA5ODY2MzksImlhdCI6MTcyOTc3NzAzOSwiYWdlX2VxdWFsX29yX292ZXIiOnsiX3NkIjpbIi1HWThEMzJGb0xCeWhlZzFWcC1WMEg2Y2UtLU15dEZKWmk5cEVzdXd1bmciLCIzVEprYTg0Z1NKZEoxYTJmTkZNcEJHbldHcUFRYW94VF8xVm5heXptUzdnIiwiRlROcFl5T0hUcGl5M1pNSk9TelNmZFNQSktyN0FfZFd0MVpZVEttTGxqSSIsIkk0b1lCRXpQMHM5bVJlcXpnTU5yWGJnemxMc3RBTTJvUmV6SThTbUdRNkkiLCJoN2YwSlB5Ui1JM09mSURJZWJPTmpNamdYdW5QQ05EeEVkSHFkc0F3WTJRIiwicmZYb1ZWYnY0ZWNkbmZBcXlUUlB6LVQ3NEhoMThqRHlOQlo4TWNIX1NBUSJdfX0.B-EwOkVtbddnmlG-nKwPHUZ5VbgsZ-iUzb8lWb6tdLr1-9CZUo1iK3poxVcHl8u3GGN3MEzju67BImFmrHdR6g~WyJHcE9kMGVRcXl4WDJvOG1OMnNwRDZnIiwiZmFtaWx5X25hbWUiLCJNVVNURVJNQU5OIl0~WyJXMVB4QmVyUWxLUkM5c1RvM0VUWmVRIiwiZ2l2ZW5fbmFtZSIsIkVSSUtBIl0~WyIzR0k1OVUxcUM4bzBmU0w1dHY3US13IiwiYmlydGhkYXRlIiwiMTk4NC0wMS0yNiJd~WyJhN1pmV1pRdFlZZU5TNmEwVGNwQ1JRIiwiYWdlX2JpcnRoX3llYXIiLDE5ODRd~WyJFbEtKc2hWMlZycnZ3Zmgtd0RwandnIiwiYWdlX2luX3llYXJzIiw0MF0~WyJsTV9ULWxqQTRSV0NkUHozVTFyMEhBIiwiYmlydGhfZmFtaWx5X25hbWUiLCJHQUJMRVIiXQ~WyJFck93ZUlzVV9FU3NMREdxZmlYOElnIiwibmF0aW9uYWxpdGllcyIsWyJERSJdXQ~WyJXLS04bjRTT094dWVEVU9GalUyMVJ3IiwiMTIiLHRydWVd~WyJVaXdLS1MxSlNGdGl6NEF3QTBqenhnIiwiMTQiLHRydWVd~WyJha0x3aGd1S2lSaG5nQVdmZ01XOEhRIiwiMTYiLHRydWVd~WyJHYXVBaUc2ZW1CT0pJX1B5WE1YdDVBIiwiMTgiLHRydWVd~WyJiMGM5NHphbGMzZ0FwQkFzUllGX0ZnIiwiMjEiLHRydWVd~WyJwd25JYTdnb2xacjA5c3I5OGpteUdnIiwiNjUiLGZhbHNlXQ~WyJvdHpyeU1yYXc2NUY0SjkyVU1zWXF3IiwibG9jYWxpdHkiLCJCRVJMSU4iXQ~WyJ2WWt2RzBqVEpzb2QwVGJieHQyT2hRIiwibG9jYWxpdHkiLCJLw5ZMTiJd~WyJ2ZWJsRU1URmJVTUx3V0FqUzYza1pnIiwiY291bnRyeSIsIkRFIl0~WyJqZTJHMnU4RFlyN1UxSEh3YWFqa3ZRIiwicG9zdGFsX2NvZGUiLCI1MTE0NyJd~WyJVM0hKdEZHNm02NFBqYmg1ZTgxaVFRIiwic3RyZWV0X2FkZHJlc3MiLCJIRUlERVNUUkFTU0UgMTciXQ~ diff --git a/test/evaluation/selectFrom.spec.ts b/test/evaluation/selectFrom.spec.ts index c694f34..e297e59 100644 --- a/test/evaluation/selectFrom.spec.ts +++ b/test/evaluation/selectFrom.spec.ts @@ -10,6 +10,7 @@ import { EvaluationClientWrapper } from '../../lib/evaluation'; import { SubmissionRequirementMatchType } from '../../lib/evaluation/core'; import { InternalPresentationDefinitionV1, InternalPresentationDefinitionV2, SSITypesBuilder } from '../../lib/types'; import PexMessages from '../../lib/types/Messages'; +import { ClaimValue } from '../types'; export const hasher = (data: string) => createHash('sha256').update(data).digest(); @@ -42,7 +43,10 @@ describe('selectFrom tests', () => { const pd = SSITypesBuilder.modelEntityToInternalPresentationDefinitionV1(pdSchema); const evaluationClientWrapper: EvaluationClientWrapper = new EvaluationClientWrapper(); expect( - evaluationClientWrapper.selectFrom(pd, wvcs, { holderDIDs: dids, limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES }), + evaluationClientWrapper.selectFrom(pd, wvcs, { + holderDIDs: dids, + limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, + }), ).toEqual({ areRequiredCredentialsPresent: Status.INFO, errors: [], @@ -164,7 +168,10 @@ describe('selectFrom tests', () => { vpSimple.verifiableCredential[2], ]); expect( - evaluationClientWrapper.selectFrom(pd, wvcs, { holderDIDs: dids, limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES }), + evaluationClientWrapper.selectFrom(pd, wvcs, { + holderDIDs: dids, + limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, + }), ).toEqual({ areRequiredCredentialsPresent: Status.INFO, errors: [], @@ -256,7 +263,10 @@ describe('selectFrom tests', () => { vpSimple.verifiableCredential[2], ]); expect( - evaluationClientWrapper.selectFrom(pd, wvcs, { holderDIDs: dids, limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES }), + evaluationClientWrapper.selectFrom(pd, wvcs, { + holderDIDs: dids, + limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, + }), ).toEqual({ areRequiredCredentialsPresent: Status.INFO, errors: [], @@ -329,7 +339,10 @@ describe('selectFrom tests', () => { vpSimple.verifiableCredential[2], ]); expect( - evaluationClientWrapper.selectFrom(pd, wvcs, { holderDIDs: dids, limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES }), + evaluationClientWrapper.selectFrom(pd, wvcs, { + holderDIDs: dids, + limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, + }), ).toEqual({ areRequiredCredentialsPresent: Status.INFO, errors: [], @@ -452,7 +465,10 @@ describe('selectFrom tests', () => { vpSimple.verifiableCredential[2], ]); expect( - evaluationClientWrapper.selectFrom(pd, wvcs, { holderDIDs: dids, limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES }), + evaluationClientWrapper.selectFrom(pd, wvcs, { + holderDIDs: dids, + limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, + }), ).toEqual({ areRequiredCredentialsPresent: Status.INFO, errors: [], @@ -576,7 +592,10 @@ describe('selectFrom tests', () => { vpSimple.verifiableCredential[2], ]); expect( - evaluationClientWrapper.selectFrom(pd, wvcs, { holderDIDs: dids, limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES }), + evaluationClientWrapper.selectFrom(pd, wvcs, { + holderDIDs: dids, + limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, + }), ).toEqual({ areRequiredCredentialsPresent: Status.INFO, errors: [], @@ -1028,9 +1047,76 @@ describe('selectFrom tests', () => { pex.evaluateCredentials(pd, result.verifiableCredential!); const presentationResult = pex.presentationFrom(pd, result.verifiableCredential!); + expect(presentationResult).toBeDefined(); const cred = await SDJwt.fromEncode(presentationResult.presentations[1].compactSdJwtVc, hasher); - await cred.getClaims(hasher); + const claims = await cred.getClaims>(hasher); + + console.log(claims); + + // Check data group 1 + expect((claims.electronicPassport as { dataGroup1: Record }).dataGroup1.birthdate).toBe('2024-10-09'); + expect((claims.electronicPassport as { dataGroup1: Record }).dataGroup1.issuerCode).toBe('d'); + expect((claims.electronicPassport as { dataGroup1: Record }).dataGroup1.sexCode).toBe('dd'); + expect((claims.electronicPassport as { dataGroup1: Record }).dataGroup1.expiryDate).toBe('2024-10-10'); + expect((claims.electronicPassport as { dataGroup1: Record }).dataGroup1.holdersName).toBe('dd'); + expect((claims.electronicPassport as { dataGroup1: Record }).dataGroup1.natlCode).toBe('d'); + expect((claims.electronicPassport as { dataGroup1: Record }).dataGroup1.passportNumberIdentifier).toBe('d'); + + // Check empty objects + expect((claims.electronicPassport as Record>).dataGroup2EncodedFaceBiometrics).toEqual({}); + expect((claims.electronicPassport as Record>).docSecurityObject).toEqual({}); + expect((claims.electronicPassport as Record>).dataGroup15.activeAuthentication).toEqual({}); + expect((claims.electronicPassport as Record>).digitalTravelCredential).toEqual({}); + + // Top level claims + expect(claims.vct).toBe('epassport_copy_vc'); + expect(claims.type).toBe('epassport_copy_vc'); + expect(claims.iss).toBe('did:web:agent.nb.dev.sphereon.com'); + }); + + it('Funke test', async function () { + const pdSchema: InternalPresentationDefinitionV2 = getFileAsJson('./test/dif_pe_examples/pdV2/pd-sd-jwt-vp-funke.json').presentation_definition; + const vcs: string[] = []; + vcs.push(getFile('test/dif_pe_examples/vc/vc-funke-pid-sd.jwt').replace(/(\r\n|\n|\r)/gm, '')); + const pd = SSITypesBuilder.modelEntityInternalPresentationDefinitionV2(pdSchema); + const evaluationClientWrapper: EvaluationClientWrapper = new EvaluationClientWrapper(); + const wvcs: WrappedVerifiableCredential[] = SSITypesBuilder.mapExternalVerifiableCredentialsToWrappedVcs(vcs, hasher); + const result = evaluationClientWrapper.selectFrom(pd, wvcs, { + holderDIDs: ['FAsYneKJhWBP2n5E21ZzdY'], + limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, + }); + expect(result.areRequiredCredentialsPresent).toBe(Status.INFO); + + pex.evaluateCredentials(pd, result.verifiableCredential!); + const presentationResult = pex.presentationFrom(pd, result.verifiableCredential!); expect(presentationResult).toBeDefined(); - // TODO finish test + const cred = await SDJwt.fromEncode(presentationResult.presentations[0].compactSdJwtVc, hasher); + const claims = await cred.getClaims>(hasher); + console.log(claims); + + // Check personal information + expect(claims.family_name).toBe('MUSTERMANN'); + expect(claims.given_name).toBe('ERIKA'); + + // Check place of birth + expect((claims.place_of_birth as { locality: string }).locality).toBe('BERLIN'); + + // Check address details + expect( + ( + claims.address as { + locality: string; + postal_code: string; + country: string; + street_address: string; + } + ).locality, + ).toBe('KÖLN'); + + // Check issuing country + expect(claims.issuing_country).toBe('DE'); + + // Check age verification + expect((claims.age_equal_or_over as { '18': boolean })['18']).toBe(true); }); }); diff --git a/test/types/index.ts b/test/types/index.ts new file mode 100644 index 0000000..8f0e5c4 --- /dev/null +++ b/test/types/index.ts @@ -0,0 +1 @@ +export type ClaimValue = string | number | boolean | { [key: string]: ClaimValue } | Array;