From 218b7b2020917fbb9210be0bb03d841eaf97c740 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 10 Apr 2025 17:07:05 +0200 Subject: [PATCH 1/3] fix: predicates and other issues Signed-off-by: Timo Glastra --- lib/PEX.ts | 21 ++- lib/evaluation/evaluationClientWrapper.ts | 1 + .../formatRestrictionEvaluationHandler.ts | 44 +++--- .../inputDescriptorFilterEvaluationHandler.ts | 27 +++- .../predicateRelatedFieldEvaluationHandler.ts | 69 ++++++++- lib/types/Messages.ts | 1 + test/PEX.spec.ts | 12 +- .../pdV1/pd-schema-invalid-predicates.json | 58 +++++++ .../pdV1/pd-schema-multiple-constraints.json | 31 +--- .../evaluationClientWrapper.spec.ts | 26 ++-- .../limitDisclosureEvaluationHandler.spec.ts | 2 +- ...icateRelatedFieldEvaluationHandler.spec.ts | 38 +---- test/predicates.spec.ts | 141 ++++++++++++++++++ .../pdMultiCredentials.ts | 3 - 14 files changed, 367 insertions(+), 107 deletions(-) create mode 100644 test/dif_pe_examples/pdV1/pd-schema-invalid-predicates.json create mode 100644 test/predicates.spec.ts diff --git a/lib/PEX.ts b/lib/PEX.ts index a7a2d4df..3660798a 100644 --- a/lib/PEX.ts +++ b/lib/PEX.ts @@ -96,6 +96,7 @@ export class PEX { throw new Error('At least one presentation must be provided'); } + let originalPresentationSubmission = opts?.presentationSubmission const generatePresentationSubmission = opts?.generatePresentationSubmission !== undefined ? opts.generatePresentationSubmission : opts?.presentationSubmission === undefined; const pd: IInternalPresentationDefinition = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition); @@ -117,11 +118,12 @@ export class PEX { !presentationSubmission && presentationsArray.length === 1 && PexCredentialMapper.isW3cPresentation(wrappedPresentations[0].presentation) && - !generatePresentationSubmission + !opts?.generatePresentationSubmission ) { const decoded = wrappedPresentations[0].decoded; if ('presentation_submission' in decoded) { - presentationSubmission = decoded.presentation_submission; + presentationSubmission = JSON.parse(JSON.stringify(decoded.presentation_submission)); + originalPresentationSubmission = decoded.presentation_submission } if (!presentationSubmission) { throw Error(`Either a presentation submission as part of the VP or provided in options was expected`); @@ -132,6 +134,16 @@ export class PEX { `unexpected presentationSubmissionLocation ${opts.presentationSubmissionLocation} was provided. Expected ${PresentationSubmissionLocation.PRESENTATION} when no presentationSubmission passed and first verifiable presentation contains a presentation_submission and generatePresentationSubmission is false`, ); } + + // We need to update the vp path as PEX decoded assumes it's an external submission + // So we need to update the submission paths + if (wrappedPresentations[0].format === 'jwt_vp') { + for (const descriptor of presentationSubmission.descriptor_map) { + if (!descriptor.path.startsWith('$.vp')) { + descriptor.path = descriptor.path.replace('$.', '$.vp.'); + } + } + } } else if (!presentationSubmission && !generatePresentationSubmission) { throw new Error('Presentation submission in options was expected.'); } @@ -167,7 +179,10 @@ export class PEX { } } - return result; + return { + ...result, + value: originalPresentationSubmission ?? result.value + }; } /*** diff --git a/lib/evaluation/evaluationClientWrapper.ts b/lib/evaluation/evaluationClientWrapper.ts index c5cd0667..177ee2f3 100644 --- a/lib/evaluation/evaluationClientWrapper.ts +++ b/lib/evaluation/evaluationClientWrapper.ts @@ -454,6 +454,7 @@ export class EvaluationClientWrapper { value: string | IVerifiablePresentation[] | IVerifiableCredential; }>; + console.log(wvp.decoded, descriptor.path) if (!vcResult) { return { error: { diff --git a/lib/evaluation/handlers/formatRestrictionEvaluationHandler.ts b/lib/evaluation/handlers/formatRestrictionEvaluationHandler.ts index bd35984b..c0f2d6ef 100644 --- a/lib/evaluation/handlers/formatRestrictionEvaluationHandler.ts +++ b/lib/evaluation/handlers/formatRestrictionEvaluationHandler.ts @@ -1,3 +1,5 @@ +import { FieldV2 } from '@sphereon/pex-models'; + import { Status } from '../../ConstraintUtils'; import { IInternalPresentationDefinition, InternalPresentationDefinitionV1, InternalPresentationDefinitionV2 } from '../../types'; import PexMessages from '../../types/Messages'; @@ -30,8 +32,29 @@ export class FormatRestrictionEvaluationHandler extends AbstractEvaluationHandle if (allowedFormats.includes(wvc.format)) { // According to 18013-7 the docType MUST match the input descriptor ID - if (wvc.format === 'mso_mdoc' && wvc.credential.docType !== _inputDescriptor.id) { - this.getResults().push(this.generateInputDescriptorIdDoctypeErrorResult(index, `$[${vcIndex}]`, wvc)); + if (wvc.format === 'mso_mdoc') { + if (wvc.credential.docType !== _inputDescriptor.id) { + this.getResults().push( + this.generateErrorResult(index, `$[${vcIndex}]`, wvc, PexMessages.INPUT_DESCRIPTOR_ID_MATCHES_MDOC_DOCTYPE_DIDNT_PASS), + ); + } + + if (_inputDescriptor.constraints?.fields?.some((field) => field.filter !== undefined)) { + this.getResults().push( + this.generateErrorResult(index, `$[${vcIndex}]`, wvc, "Fields cannot have a 'filter' defined for mdoc credentials (ISO 18013-7)."), + ); + } + + if (_inputDescriptor.constraints?.fields?.some((field: FieldV2) => field.intent_to_retain === undefined)) { + this.getResults().push( + this.generateErrorResult( + index, + `$[${vcIndex}]`, + wvc, + "Fields must have 'intent_to_retain' defined for mdoc credentials (ISO 18013-7).", + ), + ); + } } this.getResults().push( @@ -46,25 +69,12 @@ export class FormatRestrictionEvaluationHandler extends AbstractEvaluationHandle this.updatePresentationSubmission(pd); } - private generateInputDescriptorIdDoctypeErrorResult(idIdx: number, vcPath: string, wvc: WrappedVerifiableCredential): HandlerCheckResult { - return { - input_descriptor_path: `$.input_descriptors[${idIdx}]`, - evaluator: this.getName(), - status: Status.ERROR, - message: PexMessages.INPUT_DESCRIPTOR_ID_MATCHES_MDOC_DOCTYPE_DIDNT_PASS, - verifiable_credential_path: vcPath, - payload: { - format: wvc.format, - }, - }; - } - - private generateErrorResult(idIdx: number, vcPath: string, wvc: WrappedVerifiableCredential): HandlerCheckResult { + private generateErrorResult(idIdx: number, vcPath: string, wvc: WrappedVerifiableCredential, message?: string): HandlerCheckResult { return { input_descriptor_path: `$.input_descriptors[${idIdx}]`, evaluator: this.getName(), status: Status.ERROR, - message: PexMessages.FORMAT_RESTRICTION_DIDNT_PASS, + message: message ?? PexMessages.FORMAT_RESTRICTION_DIDNT_PASS, verifiable_credential_path: vcPath, payload: { format: wvc.format, diff --git a/lib/evaluation/handlers/inputDescriptorFilterEvaluationHandler.ts b/lib/evaluation/handlers/inputDescriptorFilterEvaluationHandler.ts index 22a4ef27..0575356e 100644 --- a/lib/evaluation/handlers/inputDescriptorFilterEvaluationHandler.ts +++ b/lib/evaluation/handlers/inputDescriptorFilterEvaluationHandler.ts @@ -42,10 +42,13 @@ export class InputDescriptorFilterEvaluationHandler extends AbstractEvaluationHa wrappedVcs.forEach((wvc: WrappedVerifiableCredential, vcIndex: number) => { this.createNoFieldResults(pd, vcIndex, wvc); fields.forEach((field) => { + const isPredicate = field.value.predicate !== undefined; + let inputField: { path: PathComponent[]; value: unknown }[] = []; if (field.value.path) { inputField = JsonPathUtils.extractInputField(wvc.decoded, field.value.path); } + let resultFound = false; for (const inputFieldKey of inputField) { if (this.evaluateFilter(inputFieldKey, field.value)) { @@ -54,6 +57,26 @@ export class InputDescriptorFilterEvaluationHandler extends AbstractEvaluationHa this.getResults().push({ ...this.createResultObject(jp.stringify(field.path.slice(0, 3)), vcIndex, payload), }); + } else if ( + isPredicate && + this.evaluateFilter(inputFieldKey, { + ...field.value, + filter: { + type: 'boolean', + const: true, + }, + }) + ) { + resultFound = true; + const payload = { result: { ...inputField[0] }, valid: true, format: wvc.format, predicate: true }; + this.getResults().push({ + ...this.createResultObject( + jp.stringify(field.path.slice(0, 3)), + vcIndex, + payload, + PexMessages.INPUT_CANDIDATE_PREDICATE_VALUE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION, + ), + }); } } @@ -109,13 +132,13 @@ export class InputDescriptorFilterEvaluationHandler extends AbstractEvaluationHa }); } - private createResultObject(path: string, vcIndex: number, payload: unknown): HandlerCheckResult { + private createResultObject(path: string, vcIndex: number, payload: unknown, message?: string): HandlerCheckResult { return { input_descriptor_path: path, verifiable_credential_path: `$[${vcIndex}]`, evaluator: this.getName(), status: Status.INFO, - message: PexMessages.INPUT_CANDIDATE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION, + message: message ?? PexMessages.INPUT_CANDIDATE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION, payload, }; } diff --git a/lib/evaluation/handlers/predicateRelatedFieldEvaluationHandler.ts b/lib/evaluation/handlers/predicateRelatedFieldEvaluationHandler.ts index 702242f4..0908d5ed 100644 --- a/lib/evaluation/handlers/predicateRelatedFieldEvaluationHandler.ts +++ b/lib/evaluation/handlers/predicateRelatedFieldEvaluationHandler.ts @@ -24,7 +24,7 @@ export class PredicateRelatedFieldEvaluationHandler extends AbstractEvaluationHa this.examinePredicateRelatedField(index, inDesc.constraints); } }); - // this.updatePresentationSubmission(pdV1); + this.updatePresentationSubmission(pd); } private examinePredicateRelatedField(input_descriptor_idx: number, constraints: ConstraintsV1 | ConstraintsV2): void { @@ -58,12 +58,37 @@ export class PredicateRelatedFieldEvaluationHandler extends AbstractEvaluationHa constraints.fields[fieldIdx].path && constraints.fields[fieldIdx].path?.includes(this.concatenatePath(results[resultIdx].payload.result.path)) ) { + const field = constraints.fields[fieldIdx]; const evaluationResult = { ...results[resultIdx].payload.result }; - const resultObject = this.createResultObject(input_descriptor_idx, resultIdx, evaluationResult, results); - if (constraints.fields[fieldIdx].predicate === Optionality.Required) { + + // We only support number with minimum/maximum for predicate type + if ( + (field.filter?.type !== 'number' && field.filter?.type !== 'integer') || + (!field.filter.minimum && !field.filter.exclusiveMinimum && !field.filter.maximum && !field.filter.exclusiveMaximum) + ) { + results.push( + this.createErrorResultObject( + input_descriptor_idx, + resultIdx, + evaluationResult, + results, + "Only 'number' and 'integer' predicate with 'minimum', 'exclusiveMinimum', 'maximum', or 'exclusiveMaximum' supported.", + ), + ); + return; + } + + if (evaluationResult.value === true) { + const resultObject = this.createResultObject(input_descriptor_idx, resultIdx, evaluationResult, results); results.push(resultObject); - } else { - resultObject.payload['value'] = true; + } else if (field.predicate === Optionality.Required) { + const resultObject = this.createWarnResultObject( + input_descriptor_idx, + resultIdx, + evaluationResult, + results, + 'Predicate is required but not applied', + ); results.push(resultObject); } } @@ -106,4 +131,38 @@ export class PredicateRelatedFieldEvaluationHandler extends AbstractEvaluationHa payload: evaluationResult, }; } + + private createWarnResultObject( + input_descriptor_idx: number, + resultIdx: number, + evaluationResult: unknown, + results: HandlerCheckResult[], + message: string, + ): HandlerCheckResult { + return { + input_descriptor_path: `$.input_descriptors[${input_descriptor_idx}]`, + verifiable_credential_path: results[resultIdx].verifiable_credential_path, + evaluator: this.getName(), + status: Status.WARN, + message, + payload: evaluationResult, + }; + } + + private createErrorResultObject( + input_descriptor_idx: number, + resultIdx: number, + evaluationResult: unknown, + results: HandlerCheckResult[], + message: string, + ): HandlerCheckResult { + return { + input_descriptor_path: `$.input_descriptors[${input_descriptor_idx}]`, + verifiable_credential_path: results[resultIdx].verifiable_credential_path, + evaluator: this.getName(), + status: Status.ERROR, + message, + payload: evaluationResult, + }; + } } diff --git a/lib/types/Messages.ts b/lib/types/Messages.ts index 411ddd60..0056e3b5 100644 --- a/lib/types/Messages.ts +++ b/lib/types/Messages.ts @@ -2,6 +2,7 @@ enum PexMessages { INPUT_CANDIDATE_DOESNT_CONTAIN_PROPERTY = 'Input candidate does not contain property', INPUT_CANDIDATE_FAILED_FILTER_EVALUATION = 'Input candidate failed filter evaluation', INPUT_CANDIDATE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION = 'The input candidate is eligible for submission', + INPUT_CANDIDATE_PREDICATE_VALUE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION = 'The input candidate is eligible for submission based on predicate value result', INPUT_CANDIDATE_IS_NOT_ELIGIBLE_FOR_PRESENTATION_SUBMISSION = 'The input candidate is not eligible for submission', INPUT_DESCRIPTOR_CONTEXT_CONTAINS_HASHLINK_VERIFICATION_NOT_SUPPORTED = "Input descriptor contains hashlink. This version doesn't support hashlink verification.", LIMIT_DISCLOSURE_APPLIED = 'added variable in the limit_disclosure to the verifiableCredential', diff --git a/test/PEX.spec.ts b/test/PEX.spec.ts index 7cdb71ce..b54fb34d 100644 --- a/test/PEX.spec.ts +++ b/test/PEX.spec.ts @@ -1181,7 +1181,7 @@ describe('evaluate', () => { it('should pass with jwt vp with submission data', function () { const pdSchema: PresentationDefinitionV2 = { - id: '49768857', + id: '00000000-0000-0000-0000-000000000000', input_descriptors: [ { id: 'prc_type', @@ -1250,12 +1250,12 @@ describe('evaluate', () => { }); }); - it('when single presentation is passed, it defaults to non-external submission', function () { + it.only('when single presentation is passed, it defaults to non-external submission', function () { const pdSchema: PresentationDefinitionV2 = { - id: '49768857', + id: '00000000-0000-0000-0000-000000000000', input_descriptors: [ { - id: 'prc_type', + id: '1', name: 'Name', purpose: 'We can only support a familyName in a Permanent Resident Card', constraints: { @@ -1277,7 +1277,7 @@ describe('evaluate', () => { const evalResult: PresentationEvaluationResults = pex.evaluatePresentation(pdSchema, jwtEncodedVp); expect(evalResult.errors).toEqual([]); expect(evalResult.value?.descriptor_map[0]).toEqual({ - id: 'prc_type', + id: '1', format: 'ldp_vc', path: '$.verifiableCredential[0]', }); @@ -1285,7 +1285,7 @@ describe('evaluate', () => { it('when single presentation is passed with presentationSubmissionLocation.EXTERNAL, it generates the submission as external', function () { const pdSchema: PresentationDefinitionV2 = { - id: '49768857', + id: '00000000-0000-0000-0000-000000000000', input_descriptors: [ { id: 'prc_type', diff --git a/test/dif_pe_examples/pdV1/pd-schema-invalid-predicates.json b/test/dif_pe_examples/pdV1/pd-schema-invalid-predicates.json new file mode 100644 index 00000000..b9e34e54 --- /dev/null +++ b/test/dif_pe_examples/pdV1/pd-schema-invalid-predicates.json @@ -0,0 +1,58 @@ +{ + "comment": "Note: VP, OIDC, DIDComm, or CHAPI outer wrapper would be here.", + "presentation_definition": { + "id": "31e2f0f1-6b70-411d-b239-56aed5321884", + "purpose": "To sell you a drink we need to know that you are an adult.", + "input_descriptors": [ + { + "id": "867bfe7a-5b91-46b2-9ba4-70028b8d9cc8", + "purpose": "Your age should be greater or equal to 18.", + "schema": [ + { + "uri": "https://www.w3.org/TR/vc-data-model/#types" + } + ], + "constraints": { + "limit_disclosure": "required", + "fields": [ + { + "path": [ + "$.credentialSubject.age", + "$.credentialSubject.details.age" + ], + "filter": { + "type": "integer", + "minimum": 18 + }, + "predicate": "required" + }, { + "path": [ + "$.credentialSubject.citizenship[*]", + "$.credentialSubject.details.citizenship[*]" + ], + "filter": { + "type": "string", + "enum": [ + "eu", + "us", + "uk" + ] + }, + "predicate": "required" + }, { + "path": [ + "$.credentialSubject.country[*].abbr", + "$.credentialSubject.details.country[*].abbr" + ], + "filter": { + "type": "string", + "pattern": "NLD" + }, + "predicate": "required" + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/test/dif_pe_examples/pdV1/pd-schema-multiple-constraints.json b/test/dif_pe_examples/pdV1/pd-schema-multiple-constraints.json index b9e34e54..6c2a0353 100644 --- a/test/dif_pe_examples/pdV1/pd-schema-multiple-constraints.json +++ b/test/dif_pe_examples/pdV1/pd-schema-multiple-constraints.json @@ -16,43 +16,16 @@ "limit_disclosure": "required", "fields": [ { - "path": [ - "$.credentialSubject.age", - "$.credentialSubject.details.age" - ], + "path": ["$.credentialSubject.age", "$.credentialSubject.details.age"], "filter": { "type": "integer", "minimum": 18 }, "predicate": "required" - }, { - "path": [ - "$.credentialSubject.citizenship[*]", - "$.credentialSubject.details.citizenship[*]" - ], - "filter": { - "type": "string", - "enum": [ - "eu", - "us", - "uk" - ] - }, - "predicate": "required" - }, { - "path": [ - "$.credentialSubject.country[*].abbr", - "$.credentialSubject.details.country[*].abbr" - ], - "filter": { - "type": "string", - "pattern": "NLD" - }, - "predicate": "required" } ] } } ] } -} \ No newline at end of file +} diff --git a/test/evaluation/evaluationClientWrapper.spec.ts b/test/evaluation/evaluationClientWrapper.spec.ts index a7aa02cc..b6e00fc9 100644 --- a/test/evaluation/evaluationClientWrapper.spec.ts +++ b/test/evaluation/evaluationClientWrapper.spec.ts @@ -48,7 +48,7 @@ describe('evaluate', () => { expect(evaluationClient.results[0]).toEqual(evaluationClientWrapperData.getInputDescriptorsDoesNotMatchResult0()); expect(evaluationClient.results[6]).toEqual(evaluationClientWrapperData.getInputDescriptorsDoesNotMatchResult3()); expect(evaluationResults.errors).toEqual(evaluationClientWrapperData.getError().errors); - expect(evaluationResults.warnings?.length).toEqual(0); + expect(evaluationResults.warnings?.length).toEqual(1); }); it("should return ok if uri in vp matches at least one of input_descriptor's uris", function () { @@ -69,7 +69,7 @@ describe('evaluate', () => { expect(errorResults.length).toEqual(0); expect(evaluationResults.value).toEqual(evaluationClientWrapperData.getSuccess().value); expect(evaluationResults.errors?.length).toEqual(0); - expect(evaluationResults.warnings?.length).toEqual(0); + expect(evaluationResults.warnings?.length).toEqual(1); }); it("should return error if uri in verifiableCredential doesn't match", function () { @@ -91,7 +91,7 @@ describe('evaluate', () => { expect(evaluationClient.results[0]).toEqual(evaluationClientWrapperData.getUriInVerifiableCredentialDoesNotMatchResult0()); expect(evaluationClient.results[6]).toEqual(evaluationClientWrapperData.getUriInVerifiableCredentialDoesNotMatchResult3()); expect(evaluationResults.errors).toEqual(evaluationClientWrapperData.getError().errors); - expect(evaluationResults.warnings?.length).toEqual(0); + expect(evaluationResults.warnings?.length).toEqual(1); }); it("should return error if all the uris in vp don't match at least one of input_descriptor's uris", function () { @@ -113,7 +113,7 @@ describe('evaluate', () => { const errorResults = evaluationClient.results.filter((result) => result.status === Status.ERROR); expect(errorResults.length).toEqual(2); expect(evaluationResults.errors).toEqual(evaluationClientWrapperData.getError().errors); - expect(evaluationResults.warnings?.length).toEqual(0); + expect(evaluationResults.warnings?.length).toEqual(1); }); it("should return ok if all the uris in vp match at least one of input_descriptor's uris", function () { @@ -134,7 +134,7 @@ describe('evaluate', () => { expect(errorResults.length).toEqual(0); expect(evaluationResults.value).toEqual(evaluationClientWrapperData.getSuccess().value); expect(evaluationResults.errors?.length).toEqual(0); - expect(evaluationResults.warnings?.length).toEqual(0); + expect(evaluationResults.warnings?.length).toEqual(1); }); it('should return info if limit_disclosure deletes the etc field', function () { @@ -154,7 +154,7 @@ describe('evaluate', () => { expect((firstWrappedVc.credential.credentialSubject as ICredentialSubject & AdditionalClaims)['etc']).toBeUndefined(); expect(evaluationResults.value).toEqual(evaluationClientWrapperData.getSuccess().value); expect(evaluationResults.errors).toEqual(evaluationClientWrapperData.getSuccess().errors); - expect(evaluationResults.warnings?.length).toEqual(0); + expect(evaluationResults.warnings?.length).toEqual(1); }); it('should return info if limit_disclosure does not delete the etc field', function () { @@ -176,7 +176,7 @@ describe('evaluate', () => { expect((firstWrappedVc.credential.credentialSubject as ICredentialSubject & AdditionalClaims)['etc']).toEqual('etc'); expect(evaluationResults.value).toEqual(evaluationClientWrapperData.getSuccess().value); expect(evaluationResults.errors).toEqual(evaluationClientWrapperData.getSuccess().errors); - expect(evaluationResults.warnings?.length).toEqual(0); + expect(evaluationResults.warnings?.length).toEqual(1); }); it('should return info if limit_disclosure deletes the etc field', function () { @@ -197,7 +197,13 @@ describe('evaluate', () => { expect((firstWrappedVc.credential.credentialSubject as ICredentialSubject & AdditionalClaims)['etc']).toBeUndefined(); expect(evaluationResults.value).toEqual(evaluationClientWrapperData.getWarn().value); expect(evaluationResults.errors?.length).toEqual(0); - expect(evaluationResults.warnings).toEqual([]); + expect(evaluationResults.warnings).toEqual([ + { + message: 'Predicate is required but not applied: $.input_descriptors[0]: $.verifiableCredential[0]', + status: 'warn', + tag: 'PredicateRelatedFieldEvaluation', + }, + ]); }); it("should return ok if vc[0] doesn't have the birthPlace field", function () { @@ -218,7 +224,7 @@ describe('evaluate', () => { expect((firstWrappedVc.credential.credentialSubject as ICredentialSubject & AdditionalClaims)['birthPlace']).toBeUndefined(); expect(evaluationResults.value).toEqual(evaluationClientWrapperData.getSuccess().value); expect(evaluationResults.errors?.length).toEqual(0); - expect(evaluationResults.warnings?.length).toEqual(0); + expect(evaluationResults.warnings?.length).toEqual(1); }); it("should return ok if vc[0] doesn't have the etc field", function () { @@ -239,7 +245,7 @@ describe('evaluate', () => { expect((firstWrappedVc.credential.credentialSubject as ICredentialSubject & AdditionalClaims)['etc']).toBeUndefined(); expect(evaluationResults.value).toEqual(evaluationClientWrapperData.getSuccess().value); expect(evaluationResults.errors?.length).toEqual(0); - expect(evaluationResults.warnings?.length).toEqual(0); + expect(evaluationResults.warnings?.length).toEqual(1); }); it('Evaluate submission requirements all rule', () => { diff --git a/test/evaluation/limitDisclosureEvaluationHandler.spec.ts b/test/evaluation/limitDisclosureEvaluationHandler.spec.ts index bcad7210..4c71b58a 100644 --- a/test/evaluation/limitDisclosureEvaluationHandler.spec.ts +++ b/test/evaluation/limitDisclosureEvaluationHandler.spec.ts @@ -70,7 +70,7 @@ describe('evaluate', () => { evaluationClient.evaluate(pd, wvcs, { holderDIDs: HOLDER_DID, limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES }); const firstWrappedVc = evaluationClient.wrappedVcs[0] as WrappedW3CVerifiableCredential; expect((firstWrappedVc.credential.credentialSubject as ICredentialSubject & AdditionalClaims)['birthPlace']).toEqual('Maarssen'); - expect(evaluationClient.results[9]).toEqual({ + expect(evaluationClient.results[5]).toEqual({ evaluator: 'LimitDisclosureEvaluation', input_descriptor_path: '$.input_descriptors[0]', message: `${PexMessages.LIMIT_DISCLOSURE_NOT_SUPPORTED}. Signature suite 'limit disclosure unsupported' is not present in limitDisclosureSignatureSuites [BbsBlsSignatureProof2020]`, diff --git a/test/evaluation/predicateRelatedFieldEvaluationHandler.spec.ts b/test/evaluation/predicateRelatedFieldEvaluationHandler.spec.ts index fe5b0402..2736f376 100644 --- a/test/evaluation/predicateRelatedFieldEvaluationHandler.spec.ts +++ b/test/evaluation/predicateRelatedFieldEvaluationHandler.spec.ts @@ -61,8 +61,8 @@ describe('evaluate', () => { '$.input_descriptors[0]', '$[0]', 'PredicateRelatedFieldEvaluation', - Status.INFO, - PexMessages.INPUT_CANDIDATE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION, + Status.WARN, + 'Predicate is required but not applied', { path: ['$', 'credentialSubject', 'age'], value: 19, @@ -107,7 +107,7 @@ describe('evaluate', () => { message: PexMessages.INPUT_CANDIDATE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION, payload: { result: { - value: 19, + value: true, path: ['$', 'credentialSubject', 'age'], }, valid: true, @@ -174,11 +174,10 @@ describe('evaluate', () => { expect(evaluationClient.results.length).toEqual(2); }); - it("should return ok if verifiableCredential's age value is matching the specification in the input descriptor", function () { + it('should return error if using non supported filter for predicate', function () { const presentationDefinition: InternalPresentationDefinitionV1 = getFile( - './test/dif_pe_examples/pdV1/pd-schema-multiple-constraints.json', + './test/dif_pe_examples/pdV1/pd-schema-invalid-predicates.json', ).presentation_definition; - presentationDefinition!.input_descriptors![0]!.constraints!.fields![0]!.predicate = Optionality.Preferred; const evaluationClient: EvaluationClient = new EvaluationClient(); evaluationClient.presentationSubmission = { id: 'ftc3QsJT-gZ_JNKpusT-I', @@ -246,38 +245,15 @@ describe('evaluate', () => { }); const evaluationHandler = new PredicateRelatedFieldEvaluationHandler(evaluationClient); evaluationHandler.handle(presentationDefinition); - expect(evaluationClient.results[4]).toEqual( - new HandlerCheckResult( - '$.input_descriptors[0]', - '$[0]', - 'PredicateRelatedFieldEvaluation', - Status.INFO, - PexMessages.INPUT_CANDIDATE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION, - { value: true, path: ['$', 'credentialSubject', 'age'] }, - ), - ); expect(evaluationClient.results[5]).toEqual( new HandlerCheckResult( '$.input_descriptors[0]', '$[0]', 'PredicateRelatedFieldEvaluation', - Status.INFO, - PexMessages.INPUT_CANDIDATE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION, + Status.ERROR, + "Only 'number' and 'integer' predicate with 'minimum', 'exclusiveMinimum', 'maximum', or 'exclusiveMaximum' supported.", { value: 'eu', path: ['$', 'credentialSubject', 'details', 'citizenship', 0] }, ), ); - expect(evaluationClient.results[6]).toEqual( - new HandlerCheckResult( - '$.input_descriptors[0]', - '$[0]', - 'PredicateRelatedFieldEvaluation', - Status.INFO, - PexMessages.INPUT_CANDIDATE_IS_ELIGIBLE_FOR_PRESENTATION_SUBMISSION, - { - value: 'NLD', - path: ['$', 'credentialSubject', 'country', 0, 'abbr'], - }, - ), - ); }); }); diff --git a/test/predicates.spec.ts b/test/predicates.spec.ts new file mode 100644 index 00000000..2732f8c3 --- /dev/null +++ b/test/predicates.spec.ts @@ -0,0 +1,141 @@ +import { PresentationDefinitionV1 } from '@sphereon/pex-models'; +import { IVerifiablePresentation, W3CVerifiablePresentation } from '@sphereon/ssi-types'; + +import { PEX, Status } from '../lib'; + +const pex = new PEX(); + +const presentationDefinition = { + id: '5591656f-5b5d-40f8-ab5c-9041c8e3a6a0', + name: 'Age Verification', + purpose: 'We need to verify your age before entering a bar', + input_descriptors: [ + { + id: 'age-verification', + name: 'A specific type of VC + Issuer', + purpose: 'We want a VC of this type generated by this issuer', + schema: [ + { + uri: 'https://www.w3.org/2018/credentials/v1', + }, + ], + constraints: { + limit_disclosure: 'required', + fields: [ + { + path: ['$.issuer'], + filter: { + type: 'string', + const: 'did:indy:local:LjgpST2rjsoxYegQDRm7EL', + }, + }, + { + path: ['$.credentialSubject.name'], + }, + { + path: ['$.credentialSubject.height'], + }, + { + path: ['$.credentialSubject.age'], + predicate: 'required', + filter: { + type: 'number', + minimum: 18, + }, + }, + ], + }, + }, + ], +} satisfies PresentationDefinitionV1; + +const vp = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/security/data-integrity/v2', + { + '@vocab': 'https://www.w3.org/ns/credentials/issuer-dependent#', + }, + ], + type: ['VerifiablePresentation'], + verifiableCredential: [ + { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/security/data-integrity/v2', + { + '@vocab': 'https://www.w3.org/ns/credentials/issuer-dependent#', + }, + ], + type: ['VerifiableCredential'], + issuer: 'did:indy:local:LjgpST2rjsoxYegQDRm7EL', + credentialSubject: { + height: 173, + age: true, + name: 'John', + }, + + proof: { + cryptosuite: 'anoncreds-2023', + created: 'sometime-ago', + type: 'DataIntegrityProof', + proofPurpose: 'assertionMethod', + verificationMethod: 'did:indy:local:LjgpST2rjsoxYegQDRm7EL/anoncreds/v0/CLAIM_DEF/82229/default', + proofValue: + 'ukgKDqXNjaGVtYV9pZNlpZGlkOmluZHk6bG9jYWw6TGpncFNUMnJqc294WWVnUURSbTdFTC9hbm9uY3JlZHMvdjAvU0NIRU1BL1NjaGVtYSBmZGJlN2NlOS1kYjE1LTQyZjktYTg2My1lOTVhOTgwOWEyMTIvMS4wq2NyZWRfZGVmX2lk2UpkaWQ6aW5keTpsb2NhbDpMamdwU1Qycmpzb3hZZWdRRFJtN0VML2Fub25jcmVkcy92MC9DTEFJTV9ERUYvODIyMjkvZGVmYXVsdKlzdWJfcHJvb2aCrXByaW1hcnlfcHJvb2aCqGVxX3Byb29mhq5yZXZlYWxlZF9hdHRyc4KmaGVpZ2h0kcytpG5hbWXcACDMqMzPzM10zIMgBMyVG0QIzM3MsMylzNvMzcyMflLM1D9_zOJEzL9yBcyCzOBSQczap2FfcHJpbWXcAQECzMfM58yCKwLMwcyQzP_Mqi3Mtl7MwxdfzM7M5TvM83xizJ3MwsyxFjtszKHM8xFONcyROszhSlDM78z5zPHMijfM4czVzIwvzLluzPbM8szVQcyYzNHMnj8lOipNzMTM3MzvWTTMgszLBTJCAkvM-cz7ZMzzzN7MpMyFNMyozPvMpszrdm83SMzTzI_Mkw1nzMxVzK7MqXFqZMzDzP1GzPrMqTTMm8y6zJk3zJ7MzwoNzKNUbMyNdAZfzKjMi8yyzL8zzLg2zKbMlszxzM_MscyIMMy_zKTMvF3MxMyxzNbM7kwqFFISzO7Mrz9kzJ1QbsynzI3M5MzDzMDMhwcczPzMpczHzPbMjMyHbczVzJzM23HM4Cx6AnIQTcz-RUF7asyOe0AUzKw2KczOMcz4zPjMpVDM9MyPbczXzIXM8gHMoXzMshfMlhAzYQTM-cyNzP46zIjM6jDMm3VsF25PzLzMojTMlsyRf8yEzPE9zKnMkl8-Kzd6BMzszLkDAWfM2Xw3zI4Ra6Fl3AA5eTJ6zOTM2AMrzPLM8My4zIHM5lFQFiVYzPJ7c8yWzNTM6isbCQRhzNfMhiHMyGHMlkA4CXsJzJZmzJjM9MynbgQgzN93dsz-PczZLczaEczwoXbcAX8DUcy7zP1uP31xO3N5zOzM3WLMoczezP7MxMzBzNRhEsz0zLdwZcyOF8z1zI7MhMy6zMfMo0HMx3Usd2bMkszZzOTMmAAQzP59zLsBzJbMkszDzIFoEWLMvsz1zJ_MlcyMR2XMicyuKMyIfMyLI1HMlMyeKcy_DMz7zJnMysyCN0rM68znzKVEzNnMxljMtMz_aMzEzNxhC8zcDQgaTcz-zMfMy8zmzNhYzOxrzLV-zKPMvjZbU8zLzPRMzNLMsMyXMMyTPcz9zOpLRW4SzLHM6czrQ2Ioc8zMcyDMr3U4zM8xzLnM7S3Mo8y6zKpPBlIwzLlsKQsyzIDMrsyHzKzM0MzSzIDMr8zbI8yCQC3M6cyDUTJELFJ8zP_Mj8zaNg1lzNHMvW8vzJFGzMsYdHBOzPPM5WzMvszrWiELI8yEbDHMh1fM6wxKzInMoMyuzKpycMzqRiFLzNXMncyoX8zIUMzyzL82Y3dazOHMjCIkFHjMuczWIQ3M9cy_AjpnzIvMzMz5zPLM9MzSXMzKPCNezNPM3Mz8FcyrQnbM7MzmzNfMlszczOrM7kHM1syBzJnM0syrzPHM0AfM81RwTMzAzJHMyxHM58yCTUpOPiVpzJLMqCs4zKBGQCfMjsy2zPTMyczuesykUj7MlszKCmFQzOxxzJDMt8yDVsykXszRFQLM_cyYzIrMpjbMgMznzJNqzK0AJDjM6k3M8syQYDvMjBrMtyskcszcYCrMmknMw8zgW8yOdnJUzK4bzJ3M9cyOzJPM_m3M6KFtg61tYXN0ZXJfc2VjcmV03ABKATHMzMzRzKzMjcy-MczKe8yAXirM_H3MgXLM-8zzPy1WGFw5OU_M_8y9zPslzP1hHWfMn0QoVczeKBTM08ygbHbMiGdMOjZDXToLzLvMpDYeBsyKKGY2zMXMtQPMsjTM8mt0HGiiaWTcAEoBzLFbaczmzKfMvQx3zOrM_8ziS8ytRszGzJDM6AbMq8y9zJ_MpczcM38sCczVGszQQMz8D8z2zJnMhA7Mu8yiGEMTzIJEGszwLMy9dQLM6cyazNDM7EcezOpizM9cS8yrzIDMo8zCK8y7zL_M8TgAV8zVo2FnZdwASszqzKHMzjwPIczsCnjMkszRbE03H2TM7g8pWS3M1MynzK4TzPjMrk5YdmIgzNHM7hXMnEt9M1IuHDYqzO3MtsybzI0WXEkEC8yHzOvMinVYzLTM42AzzNgyzJMHYMy4zIPM6TtrUMylom0y3AEwzJsUzMlocz3MjE9BDgdGzJ8rzIhZLsyazOXM4szRzKR0zPTM80cdeE3Mi1w4I8zfzKIizJVbzL8xzJ9uzJTMjHBaNszhf8ygzMnMiDVeTszqzKnMs8yzzJA0zMPM7MzlzMzM28y7FMyXzLhPzMXM1MyAzJDM5mnM2Myab8yKF0ljzKHM_sy_zMnMjczuzI_M0Mzva8zmIHLMhsyhzO8gzNvM28yPzN0mzLxazMgbzJDMtCQrzO3MqQzM0czzzIMSJGQzzLbM5MygJArM48z7zKzMzi5Afcy0zI5tE8yqzIgazKRBEszwzN_M9syYPmcgzP4bzJl7a8ysUczMDB8cGMyozL9izN3M3gRSDD5kZznM3FQJzIDMpCgtzM_M8MyJzMnMsR1iQMztzM3MqMzQzJnMtsymzMHM_RBZZMyWzIlczOxmzPTMnW7MsxFPZ0pPZTLMmsyAzItBBTjM6cyAX8zGzOvMwxbM2sy_YczozNsdzMYfzOo7J8yczObMxczVVsyeZszkYcyddcyjzIvM9GPM28z_Vw9-zLXMn8z7zI1OCFgUzIFazNjMsszzXszszPwvJMzjzNvMwGrM5TcszOfMlxLMz3YWzJLMnsz0zNtXcBFtzNOpZ2VfcHJvb2ZzkYahdYShMdwASgrM4G0NzNQ_GQnM8syRKEbM7HULJMzpzOZDzJhyzIfMk8zkISwWzPY2EcyszMt6zOZQzP7Mmn0NQ1HMncziK8y7zKHM_cySOMyeY8ysGDPMiXbMu8yQKszUzLlSOl_MoszLzN_MwjvMscyLzLrM6CGhMtwASl8JJXVsCB4MzPAhDxPM4sy7Rz0BXEXM11ZbzK3MhWLMyGfMiczqzPIwzM7M9UXM5My2bnfMugVTbx7MtMyWzOfMlsy1zOVLzMhASy_M28y0DGAmzLlTzMbMpCVnXX7M6cynBsywzNjMlcy1oTDcAEoVzI9HYczzzOR0zJ5VzKBKzIzM5szZNwRCzO_M78zsCczzzL0szIDMiFQdzPrMkMyczN3Mv8yqzMvMw8zHW8zFzNM-ajRdDMzhzLfM5lPMtMy3MgvM_sz4zNkWzKt6zNwoYsywzOTMilI2e3EqIgPM40yhM9wASiwJzPhQzIcAGsyLK31zPmHM9jVKF3ZRXMy0GV3MgMyaMcy3PczbdTAwzJzM_DjM0zPM0mvM9FLMmszFYczOzNkWzLFLD8ylzNMtzKzMyznMxWHMwjFTMMySzN3MtBtkJ03M-nvMuT84oXKFpURFTFRB3AEqGg_Mj8zszNTM91MfCsylzOLMisy7zLzMjcyIzJ7M7n7M9kgBzOXMlcy9GcyOMcz3L8z-dxMPOUTM1sypcn3MmFHMrwPMlEBDPszJzIHMl8zVWxwszK7MpsyyzLpDcczYzOtkZ3TMoszrzJtwzOgxCszTUsz8zMrM1MzAzJM_PVnMlsyYzPwAbszUzNzM_wctfcz_RRPM4czRzK7MqMz0zOnM8xTMrznMv2EqT8y1zJt5zJxdzP3MqMyazLDMysz9zNvMxV1SfMyfzNMzzJlSzLhYzMZIzI8XMABWzLBSzN_M4xvM8n7M9cyfNszrzOLM_syZzJ4gzPEwzLhCzJTMw8ykzMfMpMyfzJrMgVbMoMz9PMyhIcz2zL8lYDFAf8yXRQ8RzL4wzJNqzN1_zL_M_QdrzLFvSczmGszRV0DMpszNzMrM6czabMyNOT09zJNGX8zqzKbM6HM0zPQgNszTRczfKzpbzLxJP0BHzJZtzMIPzM_MlcyRaA_MkczFzMnM3sz6zMwXJ8yhzJZIzI_M28yxzLvM-DzM6sz1zL_Mg3_MiMy6zLtvzO8oZcynasz7DsztzPN7LUHM4UXMsk0GzNLMnszPzNA7zMXMtMyDzOhJoTLcASpHzLbM9MyhzMlNzIkfzJXMx8yXzOnMz250CcyQG37MlAMSzK4ezJQLzLHMwMyazKBuc1PM9QUwzKhia8y_zOQhzOPMgMzuA8zZzIwtzJsTzK_Mk2TM48zrzPQozKAeIVsFzIHM5MzHbcymzMg0PUHM8xbM0VpazMw4zNgvY8y0dcz7GTfM2sz-zJfMm07MyR1yzN5IzN7M0xLMk8yrzN5iTkXMoczXPFx0TMy0zKTM68zXZcyrSETMssyIC0_M6QfMxms6zOjMxszkVszPb8yezLFkzJk2bjHMwS82eszmMVAFb8zFzItyzNM0PS9rCh8cWczrzNdLzPzMxMzazKgjzMsnAC0mzN_Mj8zqYGdkzK8izPt6TgUNzIgKG3DM48zCYX_M8UrMkMzBQSR7BiLM1V_MtANZexDMkcyszP3M2szje8zIzIU-zIXMx8yIY1LMkMySIGo6zMfM9S7MvczZUwhzST9QzMQwfMzRzIQpJjRDzIjMln_M4TxOS8yxfsyhzIHM-hNPzPE8enjM-h87P8ykAsy8zO9SDczHzNRUWsyazIXMnMzDzJDM1DVHzJHMq2fMuMyzQqEz3AEqH3nM1lXMrsyPzKHM0sz1zPxQQcyLzKjM62pRzM3M63gCzPvMlMyDCcyaUXDMmsyWfHESzOTM4jQrMUgOzKDMj8yczPvM6MykGyFXzPBtzPNvKg7Mq3fMicy_zMAYajLMiArMqczZRMzuzOrM9iPMmV17zOHM2cy3AMy3Lsy5AGbM_Mz6zLtLzJ_MxczxzKHMmRsHLMzCzM7Mw2Y0zITMsEFPzIHM4jzMlMzBX2RKFjHMi8zJzPkIPsyjzMgkzMk1OcyZzLA9QTIzZXUYV8yJzLXM6My0zP7M58z8Zx42LcyZzL0tXgUDzIvMmsyCzIXM28zuzL44dMzDa8zSWALMoczrYlHMkMzOKcyLXsy_zPNNzPs2zJTMs8zTzODM7cyCU0_MhczAC8zZSG5kQsybURs6zKojdcyezMAxacytzLxaP0NSzIfMocybzNxXcg9hzKPM0UrM5cyGJA5TzJM3RVN1zPdLUMzazNU9zN_M5syzzPLMjSDM78zNMszPzPDMzG_M-My_LDdcC0sFzKTM3lRUG8zHbsyszPZFzN7Mscz_K8ygQcz2Dj48zPFTf8zOzKMFzJ4mUFQozOEdzJBRLqEw3AEqEDXMjszzMyQPzLbM1kLMxzV1asyKzIs5TMzSzMbM6xrM0nPMkszlR3vMjsyIzLFYzIQWYMz8zN7M1szBNMzQecybVwcVzOzMuCt_zLJvzMnM5z3M8kDMtnMzHyUtEgk1dMzQzMI9zPpZzKR_zPZEfsyvzIANzPIxYzTMh8yczOo4zOLMwGlcKQgSzMjMnszYzPQBzO9qzJYVzOcjEMzrdcyBJyp9IcyVeszEBHp-zNQXzOjM7nkuU8yXXl3M1cyEOczlzMLM4MzdMnYiTQ1URMyIzMrMsxfM3szIzPt4JhzMhDJzBczuPhbMjSjMhz7Mgsz_UszcNszxzJHMiCAzLMymzPLMoszHMMzwzNbMtcyBWszjzOAIzO_MvjgGzIlbWszTzOcyF8yLzMELY8yqzKAdzLnM9syJzKglZEVHBszZcl7M6BlePU7MmSNwXjkrzOXMwsyNS8zuEnbMvMy-KszdKGPM_syYzMHM3syzzL0RzN0vN8y_zJbM5EHMrMz2WncPdszzM8yKY8ySaSZ0zMgpYsz1Z8zLCXrM5MzcUBXM7cyNzMTMicy4QjLMtMyvzMnMrczDXE_M4aEx3AEqHlTMyczPzJbMo8y1T8yPAUk0XsySCXUEzKhTzPIDMThnzN_Mhsz0IcyAzNLMjHjMkAsvzLXMq8zTVhzM3szuzPN_zK_MpkzMshM6zLHMyczWzPXMtMysXcyYd8zVzLlXI0rMyszszKJQzI_MlmXM7n1gzKUpO8y3KkXM78zgF8y_zPXMmDzM3MzEDCrMwMzWNA5HzIvM4i7MiMzYzLjMpCAqMsyIfHrMu8y-zOfM8i3M9CvM839jzOImzPTM28zQzMdwzL3Mnj7MtMzkzKzMz1bM52xOD8y0NGZnzP3M6czmzMIzzIvMxUfM_zLMrUo6Dh1UzKx1AkDMi3bM8DZJzIvMmn1UzPXM4cygNcz2zJjMwgwmzMrM-szxzMjM9EIlKcyMzJjMtsyrzLoXzI3MgBTMuRrM6QzM1wlhzIsOPx9-zI_M7mDMw1rMwT7MkTjMsMy2zJxWKUPMosyAfkdtAQbMmyTMmMyXWQ7M1czCzI1iQsync242zK3M9sz_zOvMj3F0zLDMzTAZdszZCChNIGTMy8y8zO49P0JTOh5YzMbMvsydJMzuzLUpzPPMv8z9zIDMhszQzIA4CsztzLTM4Xx0TMyMzPaibWrcAErM6syhzM48DyHM7Ap4zJLM0WxNNx9kzO4PKVktzNTMp8yuE8z4zK5OWHZiIMzRzO4VzJxLfTNSLhw2KsztzLbMm8yNFlxJBAvMh8zrzIp1WMy0zONgM8zYMsyTB2DMuMyDzOk7a1DMpaVhbHBoYdwBXQZTTszGe8z6zKXMo0FWIzvMjsyEHmZObHMpAmE_zNDMg2B-KxzMjcyezJHM6cyazL4fzJXMnkpDzL7MzcyLzN5GzNtgGsyyzL7M119rX8ztbh0azMHMw8zeI3zMm8ySXxIHzOPMk1UibRB_BMyaTTfMmMzuLHdjzLFyTcyozLfMqQsIU0_M2yLMqMzhzJRWN3EXRxnMiSVSBxPMisz7zIbM-3nMr8yANVnMtszTRVzM71PMuszSzJDMt8yeGkABGsycGMy3zKjM3nzMusyqzJY9TMz-zMnMvH8XzOZMf8zMzILM5WrM5wIvzPgDLczGzJrMyMzHZSzM6TTMny1rzLU4zIPMxB7M-8z1Tcy7zJLM4MzsNDt7zJHMlcyaP8ykzLZpzP3M4FZkaszAzORgzMDMscysIMz-XcyczOAdSFDMwMzMPBQ_zPzM2czqzMDMiD3MtcyxB8yrX8zZzOJNKDjMt0wKKcz3V8ytAQ5PzIHM7nhrzNLMzczEzITM28yAzLxbansozODMpczozN3MzU3Mr3LM9czie8yuF3hyzNh6zP_MzmxhDknM2mnM18z-zOrM8UMZZAnM-mHMoFjMv8ygzMfMt3xmzMHMqsyJbsyFXMyNdVfMkXV8zN0hzMhiDczydlnM0MznzOfM7H7Mwx8HGjwRfszwG8yqzJPMuk8qzIMVzK3Mlsz_zPehdIWhMtwBAQLM1A5oSQNBb8yWc8zBzIbM_lMoHnnMwcyEzOIizJLMscz8fcy-zIvMmRN1zL7Mxg5nzLNGdE_M5mPMpcyezK7MmDrMoj_M5xogzPImzOAAzNYCzITMsThsDcy5Bcy_zJV7zNnMsMyAGnYHb8yjzMzM-3bM60zMi8z8zNbMuy7Mw8z_zI7MyczzV8yjVTF-zNVkKsyyHczazJ3MllbMwDvMlBATzIJjzIcQzJ_M32AIzKJ-A8zHzK7MwTNFzNBaIEjMrsyFIMzlMXZ3zPMLzM5MzPHM6MzBzPxHB8yzNEgIaszmzJLM0cydzMxnO8yBWcz0zKVlzPYbzOjMpxrMhXhuzKsPM8yhQmzMuszMzObMlEHM2ELMmMyMQ8zOzIUdBsydHGNwT8yne394zPLM98zSzLnM4EzM8jheNTN1EczlzMRIN1rM68yQAT3MlgtZRAbMkMz9AczrzMPMsHpvzNUVzIHMx8y4zM4pzMYFb3hAGczVelTMrFbMqQbMpGAUoTHcAQDMs8z3zIAadsyrZyVEHVkKSMzHzPLMqcyDzNYnzPPMwsyTzMxOzIxZRk3MqW4ZzOrMinHM48yzC3jMzczKMC4YzKzMpcyNzI_M3WLM8krM2QEyLBXMuE4ZzLXMjMySzOjM6TjM-DTM_QkRfh3M6MyozN5lbMyBCTVgcMyNzPwKzPPM5cyTMcyezKF2PszCzM9aWB3MuMzZzNVDIB_MmMyxBy53QjEozJ5AJjdpzKk7zL7My8y-zNR4TszRzPxnEwrM0szAK11JMR_MsGnMhczFV8zhzJbMv8yiWXc-eMyczL5UKsyKzMHM_1EvzMZ2zN0HzOsxzNrMn8z_zJMyzODMs8zBMsyezJ_MwDINzN4yzN8pzLbMkMyhzLrMwDDM1wnMiirMvk8LccyFzI9azMLMp3XMzMzxzN44zPDMiszCIcz2zNgVzMHM4syaYcz5AMzDzOlJEszHzK_M08zoIsy4zKjM2cy1Q1lzzJnM68zEzNhIzLcVzKnMvcyWzJBIzNvM7ifM78zUUMyHbqVERUxUQdwBAMy7zLFDaE0aG8y0zMgUKGTM_lN2N2g6VszkzJg2JUHMqHN6zNfM7FPMj8yaCn86bMzcCTVzzKDMqMykCCbMmjjMkk_Mucz3I1PM7nDMyQjM7mc_CcybE8zPbQ_M22pvKz_Mgsy3zLcVzJXM0syDIszlzMrMsczJMcykf8z9zNF6B1BjzLXMvszzSsy9zKDMm3DMtcy0BEIVzOBezN3MiHfM6szfb8ybKw_MpifMksy6NkFnzOrMs1MMH8z1zNLMlszezKRTzLMbzLXMmsyszOhczJLMqMyTzK3MoW1ozIsGzIHMihpfzKnM4XJicMzFfcy-V8y3eDbM98zLMWzMqcySMF3MvsyMAGZbAmTMhMyyzItoQ8z8Dcy4AFjM0MzgQkTMnCvM98zoYcykzN9_zNrM5AzMxczwYczzzJRBAcy-zPQezKwOVsy0EGkYHFnM7cy5zO4PzM10QszBRczEHMz_SsyczPYuTkxYzIQwDMzcNzxuzNLMhcytFMy-oTPcAQEBd8ysf8yAzJLM2GvMmUnMkkcizMHMlsyvzPA2zJYqzN0uzLXMgcymzMMBGsy6SnHMiVbM6czOPcywzLbMv8yDUyLMp0kvSMyGNk93zKFIBS1kS8yBYlnMlWpQDMyPA1bM8kp8LSnMlhcsT8zBFGrMn8yAzLfM6Mz2zJ8_zLkFcSw2zLpqzKQ3ACrMqczXWXfM6MzIzNNLVszEzLXMx3R5QMzzzJ3MiRxLzJ4azNl8zI4NJHHM2sySzKfMm8zMzPPM0szfVH7Mn2bM2cyGzJQvblAGSMyszILM8cz4EczEzKsxJWYiMcy8zKXMg8zILMzYb0PM8cy1zK9SCMyGS8zqzPrMoRbM7szeWAINzL4pRzgyzMjM-3HMv8ztEifM_QPM-MzfTszAzOtiZcyszIxBzJzM9WAtzLHMxszjzMnM1jvMtMyTQ37MoMyRzPMEzOE1KQHMoMyEMczEXzVZzNZezPItBsy9fszzZQ5xzNFCzLbMusyrAMzHzKLMpczazJECDqEw3AEBAgYLa8yazLYwEMzCXzcmCcyizPJyzK3MlwUsb8zWzLULzKTMgW3MuRjMlEoZHcyuzNnMpRTMw2PM2UZ0zPLMn0TM_8y8zOXM3j9czI_M0sz3zKbM28zmDxYbzPPM1lfMzgcJzLdAzM_M7jDMoszzzIvMsFxpzM3MlczUeMy-zIRAzLUzzI_M3MzEbgQazJ5KzKHMiTgLKQHMs8zXzLIOzKXMt3oEaDNDa8zvJsytzLEPzJDMr1dkbMzXVS1_PRsXzNDMzczxfsz4EBwLIMybKsy7zJ_M4MzKzM_MvszOzOTMlMyUazrM58ywNcz9zM_M9mgJzPR9zKXMgVpszJQhM8yQCczlzPs5A8yszOJLaczwe8zdzKTMm3l5Lyh9HWHM7gxyzMvMqSoIF2rMrczFRczfzNMgzKkAFMyhzKhscsyyzM3M72DM78zFzIM2zMXMpsy8zJhXSszBdczNzJMvExoJOszLXgbMzGLMpMyLMszRzMsBFVAwFczGZQvMpVZtqXByZWRpY2F0ZYOpYXR0cl9uYW1lo2FnZaZwX3R5cGWiR0WldmFsdWUSr25vbl9yZXZvY19wcm9vZsA', + }, + issuanceDate: '2025-04-10T09:58:01.373519Z', + }, + ], + + proof: { + cryptosuite: 'anoncreds-2023', + type: 'DataIntegrityProof', + proofPurpose: 'authentication', + created: 'sometime-ago', + verificationMethod: 'did:indy:local:LjgpST2rjsoxYegQDRm7EL/anoncreds/v0/CLAIM_DEF/82229/default', + proofValue: + 'ukgOBqmFnZ3JlZ2F0ZWSCpmNfaGFzaNwAIFrMyWLM-8yhzNd7MMzrzL3MoSXMzRfM_nbMgHbM6VjMxMybzK_MtmzM_MzJzKRfdczqUqZjX2xpc3SW3AEBAszHzOfMgisCzMHMkMz_zKotzLZezMMXX8zOzOU7zPN8YsydzMLMsRY7bMyhzPMRTjXMkTrM4UpQzO_M-czxzIo3zOHM1cyML8y5bsz2zPLM1UHMmMzRzJ4_JToqTczEzNzM71k0zILMywUyQgJLzPnM-2TM88zezKTMhTTMqMz7zKbM63ZvN0jM08yPzJMNZ8zMVcyuzKlxamTMw8z9Rsz6zKk0zJvMusyZN8yezM8KDcyjVGzMjXQGX8yozIvMssy_M8y4NsymzJbM8czPzLHMiDDMv8ykzLxdzMTMsczWzO5MKhRSEszuzK8_ZMydUG7Mp8yNzOTMw8zAzIcHHMz8zKXMx8z2zIzMh23M1cyczNtxzOAsegJyEE3M_kVBe2rMjntAFMysNinMzjHM-Mz4zKVQzPTMj23M18yFzPIBzKF8zLIXzJYQM2EEzPnMjcz-OsyIzOowzJt1bBduT8y8zKI0zJbMkX_MhMzxPcypzJJfPis3egTM7My5AwFnzNl8N8yOEWvcAQECBgtrzJrMtjAQzMJfNyYJzKLM8nLMrcyXBSxvzNbMtQvMpMyBbcy5GMyUShkdzK7M2cylFMzDY8zZRnTM8syfRMz_zLzM5czeP1zMj8zSzPfMpszbzOYPFhvM88zWV8zOBwnMt0DMz8zuMMyizPPMi8ywXGnMzcyVzNR4zL7MhEDMtTPMj8zczMRuBBrMnkrMocyJOAspAcyzzNfMsg7Mpcy3egRoM0NrzO8mzK3MsQ_MkMyvV2RszNdVLX89GxfM0MzNzPF-zPgQHAsgzJsqzLvMn8zgzMrMz8y-zM7M5MyUzJRrOsznzLA1zP3Mz8z2aAnM9H3MpcyBWmzMlCEzzJAJzOXM-zkDzKzM4ktpzPB7zN3MpMybeXkvKH0dYczuDHLMy8ypKggXasytzMVFzN_M0yDMqQAUzKHMqGxyzLLMzczvYMzvzMXMgzbMxcymzLzMmFdKzMF1zM3Mky8TGgk6zMteBszMYsykzIsyzNHMywEVUDAVzMZlC8ylVm3cAQDMs8z3zIAadsyrZyVEHVkKSMzHzPLMqcyDzNYnzPPMwsyTzMxOzIxZRk3MqW4ZzOrMinHM48yzC3jMzczKMC4YzKzMpcyNzI_M3WLM8krM2QEyLBXMuE4ZzLXMjMySzOjM6TjM-DTM_QkRfh3M6MyozN5lbMyBCTVgcMyNzPwKzPPM5cyTMcyezKF2PszCzM9aWB3MuMzZzNVDIB_MmMyxBy53QjEozJ5AJjdpzKk7zL7My8y-zNR4TszRzPxnEwrM0szAK11JMR_MsGnMhczFV8zhzJbMv8yiWXc-eMyczL5UKsyKzMHM_1EvzMZ2zN0HzOsxzNrMn8z_zJMyzODMs8zBMsyezJ_MwDINzN4yzN8pzLbMkMyhzLrMwDDM1wnMiirMvk8LccyFzI9azMLMp3XMzMzxzN44zPDMiszCIcz2zNgVzMHM4syaYcz5AMzDzOlJEszHzK_M08zoIsy4zKjM2cy1Q1lzzJnM68zEzNhIzLcVzKnMvcyWzJBIzNvM7ifM78zUUMyHbtwBAQLM1A5oSQNBb8yWc8zBzIbM_lMoHnnMwcyEzOIizJLMscz8fcy-zIvMmRN1zL7Mxg5nzLNGdE_M5mPMpcyezK7MmDrMoj_M5xogzPImzOAAzNYCzITMsThsDcy5Bcy_zJV7zNnMsMyAGnYHb8yjzMzM-3bM60zMi8z8zNbMuy7Mw8z_zI7MyczzV8yjVTF-zNVkKsyyHczazJ3MllbMwDvMlBATzIJjzIcQzJ_M32AIzKJ-A8zHzK7MwTNFzNBaIEjMrsyFIMzlMXZ3zPMLzM5MzPHM6MzBzPxHB8yzNEgIaszmzJLM0cydzMxnO8yBWcz0zKVlzPYbzOjMpxrMhXhuzKsPM8yhQmzMuszMzObMlEHM2ELMmMyMQ8zOzIUdBsydHGNwT8yne394zPLM98zSzLnM4EzM8jheNTN1EczlzMRIN1rM68yQAT3MlgtZRAbMkMz9AczrzMPMsHpvzNUVzIHMx8y4zM4pzMYFb3hAGczVelTMrFbMqQbMpGAU3AEBAXfMrH_MgMySzNhrzJlJzJJHIszBzJbMr8zwNsyWKszdLsy1zIHMpszDARrMukpxzIlWzOnMzj3MsMy2zL_Mg1MizKdJL0jMhjZPd8yhSAUtZEvMgWJZzJVqUAzMjwNWzPJKfC0pzJYXLE_MwRRqzJ_MgMy3zOjM9syfP8y5BXEsNsy6asykNwAqzKnM11l3zOjMyMzTS1bMxMy1zMd0eUDM88ydzIkcS8yeGszZfMyODSRxzNrMksynzJvMzMzzzNLM31R-zJ9mzNnMhsyUL25QBkjMrMyCzPHM-BHMxMyrMSVmIjHMvMylzIPMyCzM2G9DzPHMtcyvUgjMhkvM6sz6zKEWzO7M3lgCDcy-KUc4MszIzPtxzL_M7RInzP0DzPjM307MwMzrYmXMrMyMQcyczPVgLcyxzMbM48zJzNY7zLTMk0N-zKDMkczzBMzhNSkBzKDMhDHMxF81WczWXszyLQbMvX7M82UOcczRQsy2zLrMqwDMx8yizKXM2syRAg7cAQDMu8yxQ2hNGhvMtMzIFChkzP5TdjdoOlbM5MyYNiVBzKhzeszXzOxTzI_Mmgp_OmzM3Ak1c8ygzKjMpAgmzJo4zJJPzLnM9yNTzO5wzMkIzO5nPwnMmxPMz20PzNtqbys_zILMt8y3FcyVzNLMgyLM5czKzLHMyTHMpH_M_czRegdQY8y1zL7M80rMvcygzJtwzLXMtARCFczgXszdzIh3zOrM32_MmysPzKYnzJLMujZBZ8zqzLNTDB_M9czSzJbM3sykU8yzG8y1zJrMrMzoXMySzKjMk8ytzKFtaMyLBsyBzIoaX8ypzOFyYnDMxX3MvlfMt3g2zPfMyzFszKnMkjBdzL7MjABmWwJkzITMssyLaEPM_A3MuABYzNDM4EJEzJwrzPfM6GHMpMzff8zazOQMzMXM8GHM88yUQQHMvsz0HsysDlbMtBBpGBxZzO3MuczuD8zNdELMwUXMxBzM_0rMnMz2Lk5MWMyEMAzM3Dc8bszSzIXMrRTMvg', + challenge: '71274858652825095395', + }, + presentation_submission: { + id: 'yv4krfh8tIMF73h_82RrM', + definition_id: '5591656f-5b5d-40f8-ab5c-9041c8e3a6a0', + descriptor_map: [ + { + id: 'age-verification', + format: 'di_vp', + path: '$.verifiableCredential[0]', + }, + ], + }, +} satisfies W3CVerifiablePresentation; + +describe('predicates', () => { + it('should return info for predicate values', () => { + const results = pex.evaluatePresentation(presentationDefinition, vp); + expect(results.areRequiredCredentialsPresent).toEqual(Status.INFO); + }); + + it('should return error if predicate value is not true', () => { + const updatedVp = JSON.parse(JSON.stringify(vp)) as IVerifiablePresentation; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + updatedVp.verifiableCredential[0].credentialSubject.age = false; + + const results = pex.evaluatePresentation(presentationDefinition, updatedVp); + + expect(results.areRequiredCredentialsPresent).toEqual(Status.ERROR); + }); + + it('should return info if no predicate value is used, but the value matches the filter is', () => { + const updatedVp = JSON.parse(JSON.stringify(vp)) as IVerifiablePresentation; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + updatedVp.verifiableCredential[0].credentialSubject.age = 22; + + const results = pex.evaluatePresentation(presentationDefinition, updatedVp); + + expect(results.areRequiredCredentialsPresent).toEqual(Status.INFO); + }); +}); diff --git a/test/test_data/limitDisclosureEvaluation/pdMultiCredentials.ts b/test/test_data/limitDisclosureEvaluation/pdMultiCredentials.ts index 966fcfe1..e15dd1a4 100644 --- a/test/test_data/limitDisclosureEvaluation/pdMultiCredentials.ts +++ b/test/test_data/limitDisclosureEvaluation/pdMultiCredentials.ts @@ -59,7 +59,6 @@ export class PdMultiCredentials { type: 'string', pattern: 'eu|us|uk', }, - predicate: 'required', }, ], }, @@ -85,7 +84,6 @@ export class PdMultiCredentials { type: 'string', pattern: 'NLD', }, - predicate: 'required', }, ], }, @@ -111,7 +109,6 @@ export class PdMultiCredentials { type: 'string', pattern: 'Maarssen', }, - predicate: 'required', }, ], }, From dc6bbc0b7f62ba6e7fac6964c3bd11e064bc5d09 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 10 Apr 2025 17:08:55 +0200 Subject: [PATCH 2/3] formatting Signed-off-by: Timo Glastra --- lib/PEX.ts | 6 +++--- lib/evaluation/evaluationClientWrapper.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/PEX.ts b/lib/PEX.ts index 3660798a..34234faf 100644 --- a/lib/PEX.ts +++ b/lib/PEX.ts @@ -96,7 +96,7 @@ export class PEX { throw new Error('At least one presentation must be provided'); } - let originalPresentationSubmission = opts?.presentationSubmission + let originalPresentationSubmission = opts?.presentationSubmission; const generatePresentationSubmission = opts?.generatePresentationSubmission !== undefined ? opts.generatePresentationSubmission : opts?.presentationSubmission === undefined; const pd: IInternalPresentationDefinition = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition); @@ -123,7 +123,7 @@ export class PEX { const decoded = wrappedPresentations[0].decoded; if ('presentation_submission' in decoded) { presentationSubmission = JSON.parse(JSON.stringify(decoded.presentation_submission)); - originalPresentationSubmission = decoded.presentation_submission + originalPresentationSubmission = decoded.presentation_submission; } if (!presentationSubmission) { throw Error(`Either a presentation submission as part of the VP or provided in options was expected`); @@ -181,7 +181,7 @@ export class PEX { return { ...result, - value: originalPresentationSubmission ?? result.value + value: originalPresentationSubmission ?? result.value, }; } diff --git a/lib/evaluation/evaluationClientWrapper.ts b/lib/evaluation/evaluationClientWrapper.ts index 177ee2f3..f08206f2 100644 --- a/lib/evaluation/evaluationClientWrapper.ts +++ b/lib/evaluation/evaluationClientWrapper.ts @@ -454,7 +454,7 @@ export class EvaluationClientWrapper { value: string | IVerifiablePresentation[] | IVerifiableCredential; }>; - console.log(wvp.decoded, descriptor.path) + console.log(wvp.decoded, descriptor.path); if (!vcResult) { return { error: { From 0c8c1ad4602484075859250a00013defa1df6873 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 10 Apr 2025 17:09:19 +0200 Subject: [PATCH 3/3] Update lib/evaluation/evaluationClientWrapper.ts --- lib/evaluation/evaluationClientWrapper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/evaluation/evaluationClientWrapper.ts b/lib/evaluation/evaluationClientWrapper.ts index f08206f2..c5cd0667 100644 --- a/lib/evaluation/evaluationClientWrapper.ts +++ b/lib/evaluation/evaluationClientWrapper.ts @@ -454,7 +454,6 @@ export class EvaluationClientWrapper { value: string | IVerifiablePresentation[] | IVerifiableCredential; }>; - console.log(wvp.decoded, descriptor.path); if (!vcResult) { return { error: {