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

Feature/sphereon 1183 sd jwt confomance #174

Merged
merged 4 commits into from
Nov 11, 2024
Merged
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
33 changes: 31 additions & 2 deletions lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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';
Expand Down Expand Up @@ -139,15 +140,43 @@
// 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);

Check warning on line 153 in lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts#L152-L153

Added lines #L152 - L153 were not covered by tests
});
return;

Check warning on line 155 in lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts#L155

Added line #L155 was not covered by tests
}

// 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<ClaimValue>(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);
}
Comment on lines +173 to +179
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious, why do we need to handle nested objects? If a PD includes $.address and address is an object, then having address: true in the disclosure frame should be enough right?

Copy link
Contributor Author

@sanderPostma sanderPostma Oct 27, 2024

Choose a reason for hiding this comment

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

@TimoGlastra
This is for the situation where a sd-jwt (not created by us) does not have a disclosure frame element on a group, but on the individual fields within a group. ie.

"electronicPassport": {
  "dataGroup1": {
     "birthdate": "{{birthdate}}",
     "expiryDate": "{{{ dateTimeAfterDays 1080 }}}",
     "passportNumberIdentifier": "{{passportNumberIdentifier}}"
  },
  "disclosureFrame": {
    "electronicPassport": {
      "dataGroup1": {
        "_sd": [
          "birthdate",
          "expiryDate",
          "passportNumberIdentifier"
        ]
      },
}

When the pd asks to disclose \$.electronicPassport.dataGroup1 instead of \$.electronicPassport.dataGroup1.* it should still disclose all disclosable fields & groups within the group.
(When you control PD or credential you can make it work by modifying either on or both, but we ran into an interoperability tests where you we do not.)
Also with the address example in the Funke PID, the @sd-jwt library (0.7.2) does not disclose anything when specifying address: true

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm okay, i think it's probably something that should be solved in @sd-jwt library then? As saying address: true is enough to know the whole address object and sub-disclosures should be resolved.

It feels like a hack to me to solve it in this was

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Forking the @sd-jwt library which is not ours is also something we would like to avoid. When I have more time I can play around with the tests in the @sd-jwt library, but have plenty of other stuff on my plate atm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For clarity, for this setup address: true does work:

 "disclosureFrame": {
    "electronicPassport": {
        "_sd": [
          "dataGroup1"
        ]
}

But then you always disclose the whole address and just field passportNumberIdentifier won't work anymore.

} else {
this.createMandatoryFieldNotFoundResult(inputDescriptorIndex, vcIndex, field.path);
return undefined;
Expand Down
9 changes: 9 additions & 0 deletions lib/utils/jsonPathUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@
return obj;
}

public static getValue<T = unknown>(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;

Check warning on line 86 in lib/utils/jsonPathUtils.ts

View check run for this annotation

Codecov / codecov/patch

lib/utils/jsonPathUtils.ts#L86

Added line #L86 was not covered by tests
}
}

private static copyResultPathToDestinationDefinition(pathDetails: (string | number)[], pd: IPresentationDefinition, newPropertyName: string) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let objectCursor: any = pd;
Expand Down
4 changes: 2 additions & 2 deletions lib/utils/sdJwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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<Record<string, unknown>>,
);

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
54 changes: 54 additions & 0 deletions test/dif_pe_examples/pdV2/pd-sd-jwt-vp-funke.json
Original file line number Diff line number Diff line change
@@ -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."
}
]
}
}
1 change: 1 addition & 0 deletions test/dif_pe_examples/vc/vc-funke-pid-sd.jwt
Original file line number Diff line number Diff line change
@@ -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~
102 changes: 94 additions & 8 deletions test/evaluation/selectFrom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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: [],
Expand Down Expand Up @@ -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: [],
Expand Down Expand Up @@ -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: [],
Expand Down Expand Up @@ -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: [],
Expand Down Expand Up @@ -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: [],
Expand Down Expand Up @@ -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: [],
Expand Down Expand Up @@ -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<Record<string, ClaimValue>>(hasher);

console.log(claims);

// Check data group 1
expect((claims.electronicPassport as { dataGroup1: Record<string, string> }).dataGroup1.birthdate).toBe('2024-10-09');
expect((claims.electronicPassport as { dataGroup1: Record<string, string> }).dataGroup1.issuerCode).toBe('d');
expect((claims.electronicPassport as { dataGroup1: Record<string, string> }).dataGroup1.sexCode).toBe('dd');
expect((claims.electronicPassport as { dataGroup1: Record<string, string> }).dataGroup1.expiryDate).toBe('2024-10-10');
expect((claims.electronicPassport as { dataGroup1: Record<string, string> }).dataGroup1.holdersName).toBe('dd');
expect((claims.electronicPassport as { dataGroup1: Record<string, string> }).dataGroup1.natlCode).toBe('d');
expect((claims.electronicPassport as { dataGroup1: Record<string, string> }).dataGroup1.passportNumberIdentifier).toBe('d');

// Check empty objects
expect((claims.electronicPassport as Record<string, Record<string, unknown>>).dataGroup2EncodedFaceBiometrics).toEqual({});
expect((claims.electronicPassport as Record<string, Record<string, unknown>>).docSecurityObject).toEqual({});
expect((claims.electronicPassport as Record<string, Record<string, unknown>>).dataGroup15.activeAuthentication).toEqual({});
expect((claims.electronicPassport as Record<string, Record<string, unknown>>).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<Record<string, ClaimValue>>(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);
});
});
1 change: 1 addition & 0 deletions test/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ClaimValue = string | number | boolean | { [key: string]: ClaimValue } | Array<ClaimValue>;
Loading