diff --git a/lib/evaluation/core/submissionRequirementMatch.ts b/lib/evaluation/core/submissionRequirementMatch.ts index 9188c1f7..a6bf3d2c 100644 --- a/lib/evaluation/core/submissionRequirementMatch.ts +++ b/lib/evaluation/core/submissionRequirementMatch.ts @@ -1,4 +1,4 @@ -import { Rules } from '@sphereon/pex-models'; +import { Status } from '../../ConstraintUtils'; export enum SubmissionRequirementMatchType { /** @@ -20,19 +20,56 @@ export enum SubmissionRequirementMatchType { * if no submission_requirements are present in the presentation definition. If the match type * is `InputDescriptor` the {@link SubmissionRequirementMatch.id} property refers to the `id` * of the `input_descriptors` entry in the presentation definition. + * + * You need to select exactly ONE of the vcs from vc_path in this case for the submission */ InputDescriptor = 'InputDescriptor', } -export interface SubmissionRequirementMatch { - type: SubmissionRequirementMatchType; - id: string | number; +export interface SubmissionRequirementMatchFromNested extends SubmissionRequirementMatchFromBase { + from_nested: Array; + + // Helps with type narrowing + from?: never; +} + +export interface SubmissionRequirementMatchFrom extends SubmissionRequirementMatchFromBase { + from: string; + + input_descriptors: SubmissionRequirementMatchInputDescriptor[]; + + // Helps with type narrowing + from_nested?: never; +} + +export interface SubmissionRequirementMatchFromBase { + areRequiredCredentialsPresent: Status; + type: SubmissionRequirementMatchType.SubmissionRequirement; + id: number; + name?: string; + + rule: + | { + type: 'pick'; + count?: number; + min?: number; + max?: number; + } + | { + type: 'all'; + count: number; + }; +} + +export interface SubmissionRequirementMatchInputDescriptor { + areRequiredCredentialsPresent: Status; + id: string; name?: string; - rule: Rules; - min?: number; - count?: number; - max?: number; + type: SubmissionRequirementMatchType.InputDescriptor; vc_path: string[]; - from?: string; - from_nested?: SubmissionRequirementMatch[]; } + +export type SubmissionRequirementMatch = + | SubmissionRequirementMatchFrom + | SubmissionRequirementMatchFromNested + | SubmissionRequirementMatchInputDescriptor; diff --git a/lib/evaluation/evaluationClientWrapper.ts b/lib/evaluation/evaluationClientWrapper.ts index 237905e5..10050a76 100644 --- a/lib/evaluation/evaluationClientWrapper.ts +++ b/lib/evaluation/evaluationClientWrapper.ts @@ -21,7 +21,7 @@ import { IPresentationDefinition, OrArray, } from '../types'; -import { JsonPathUtils, ObjectUtils } from '../utils'; +import { JsonPathUtils } from '../utils'; import { getVpFormatForVcFormat } from '../utils/formatMap'; import { @@ -30,6 +30,9 @@ import { PresentationEvaluationResults, SelectResults, SubmissionRequirementMatch, + SubmissionRequirementMatchFrom, + SubmissionRequirementMatchFromNested, + SubmissionRequirementMatchInputDescriptor, SubmissionRequirementMatchType, } from './core'; import { EvaluationClient } from './evaluationClient'; @@ -79,276 +82,250 @@ export class EvaluationClientWrapper { restrictToDIDMethods?: string[]; }, ): SelectResults { - let selectResults: SelectResults; - this._client.evaluate(presentationDefinition, wrappedVerifiableCredentials, opts); + const warnings: Checked[] = [...this.formatNotInfo(Status.WARN)]; const errors: Checked[] = [...this.formatNotInfo(Status.ERROR)]; + const marked = this._client.results.filter((result) => result.evaluator === 'MarkForSubmissionEvaluation' && result.status !== Status.ERROR); - if (presentationDefinition.submission_requirements) { - const info: HandlerCheckResult[] = this._client.results.filter( - (result) => result.evaluator === 'MarkForSubmissionEvaluation' && result.payload.group && result.status !== Status.ERROR, - ); - const marked = Array.from(new Set(info)); - let matchSubmissionRequirements; - try { - matchSubmissionRequirements = this.matchSubmissionRequirements( - presentationDefinition, - presentationDefinition.submission_requirements, - marked, - ); - } catch (e) { - const matchingError: Checked = { - status: Status.ERROR, - message: JSON.stringify(e), - tag: 'matchSubmissionRequirements', - }; - return { - errors: errors ? [...errors, matchingError] : [matchingError], - warnings: warnings, - areRequiredCredentialsPresent: Status.ERROR, - }; - } - - const matches = this.extractMatches(matchSubmissionRequirements); - const credentials: IVerifiableCredential[] = matches.map( - (e) => - jp.nodes( - this._client.wrappedVcs.map((wrapped) => wrapped.original), - e, - )[0].value, - ); - const areRequiredCredentialsPresent = this.determineAreRequiredCredentialsPresent(presentationDefinition, matchSubmissionRequirements); - selectResults = { - errors: areRequiredCredentialsPresent === Status.INFO ? [] : errors, - matches: [...matchSubmissionRequirements], - areRequiredCredentialsPresent, - verifiableCredential: credentials, - warnings, + let matchSubmissionRequirements: SubmissionRequirementMatch[]; + try { + matchSubmissionRequirements = this.matchPresentationDefinition(marked, presentationDefinition); + } catch (e) { + const matchingError: Checked = { + status: Status.ERROR, + message: JSON.stringify(e), + tag: 'matchSubmissionRequirements', + }; + return { + errors: errors ? [...errors, matchingError] : [matchingError], + warnings: warnings, + areRequiredCredentialsPresent: Status.ERROR, }; - } else { - const marked: HandlerCheckResult[] = this._client.results.filter( - (result) => result.evaluator === 'MarkForSubmissionEvaluation' && result.status !== Status.ERROR, - ); - const checkWithoutSRResults: HandlerCheckResult[] = this.checkWithoutSubmissionRequirements(marked, presentationDefinition); - if (!checkWithoutSRResults.length) { - const matchSubmissionRequirements = this.matchWithoutSubmissionRequirements(marked, presentationDefinition); - const matches = this.extractMatches(matchSubmissionRequirements); - const credentials: IVerifiableCredential[] = matches.map( - (e) => - jp.nodes( - this._client.wrappedVcs.map((wrapped) => wrapped.original), - e, - )[0].value, - ); - selectResults = { - errors: [], - matches: [...matchSubmissionRequirements], - areRequiredCredentialsPresent: Status.INFO, - verifiableCredential: credentials, - warnings, - }; - } else { - return { - errors: errors, - matches: [], - areRequiredCredentialsPresent: Status.ERROR, - verifiableCredential: wrappedVerifiableCredentials.map((value) => value.original), - warnings: warnings, - }; - } } - this.fillSelectableCredentialsToVerifiableCredentialsMapping(selectResults, wrappedVerifiableCredentials); - selectResults.areRequiredCredentialsPresent = this.determineAreRequiredCredentialsPresent(presentationDefinition, selectResults?.matches); - this.remapMatches( - wrappedVerifiableCredentials.map((wrapped) => wrapped.original as IVerifiableCredential), - selectResults.matches, - selectResults?.verifiableCredential, - ); - selectResults.matches?.forEach((m) => { - this.updateSubmissionRequirementMatchPathToAlias(m, 'verifiableCredential'); + const allVcPaths = this.extractVcPathsFromMatches(matchSubmissionRequirements); + const credentials: Array<{ credential: IVerifiableCredential; originalIndex: number }> = allVcPaths.map((vcPath) => { + const credential = jp.nodes( + this._client.wrappedVcs.map((wrapped) => wrapped.original), + vcPath, + )[0].value; + + const originalIndex = this._client.wrappedVcs.findIndex((wrapped) => wrapped.original === credential); + if (originalIndex === -1) { + throw new Error('Unable to find original index for credential'); + } + + return { + credential, + originalIndex, + }; }); - if (selectResults.areRequiredCredentialsPresent === Status.INFO) { - selectResults.errors = []; - } else { - selectResults.errors = errors; - selectResults.warnings = warnings; - selectResults.verifiableCredential = wrappedVerifiableCredentials.map((value) => value.original); - } + const areRequiredCredentialsPresent = this.determineAreRequiredCredentialsPresent(matchSubmissionRequirements); + const vcIndexes = credentials.map((c) => c.originalIndex); + + const selectResults: SelectResults = { + errors: areRequiredCredentialsPresent === Status.INFO ? [] : errors, + matches: matchSubmissionRequirements.map((match) => + this.remapVcPathsOnMatchToSelectableCredentialIndexes( + match, + vcIndexes, + wrappedVerifiableCredentials.map((wrapped) => wrapped.original as IVerifiableCredential), + ), + ), + areRequiredCredentialsPresent, + verifiableCredential: credentials.map((c) => c.credential), + warnings, + vcIndexes, + }; + return selectResults; } - private remapMatches( - verifiableCredentials: OriginalVerifiableCredential[], - submissionRequirementMatches?: SubmissionRequirementMatch[], - vcsToSend?: OriginalVerifiableCredential[], - ) { - submissionRequirementMatches?.forEach((srm) => { - if (srm.from_nested) { - this.remapMatches(verifiableCredentials, srm.from_nested, vcsToSend); - } else { - srm.vc_path.forEach((match, index, matches) => { - const vc = jp.query(verifiableCredentials, match)[0]; - const newIndex = vcsToSend?.findIndex((svc) => JSON.stringify(svc) === JSON.stringify(vc)); - if (newIndex === -1) { + private remapVcPathsOnMatchToSelectableCredentialIndexes( + match: Match, + selectableIndexes: number[], + originalVerifiableCredentials: OriginalVerifiableCredential[], + ): Match { + if (match.type === SubmissionRequirementMatchType.InputDescriptor) { + return { + ...match, + vc_path: match.vc_path.map((vcPath) => { + const vc = jp.query(originalVerifiableCredentials, vcPath)[0]; + const originalIndex = originalVerifiableCredentials.findIndex((original) => original === vc); + const selectableIndex = selectableIndexes.findIndex((index) => index === originalIndex); + + if (selectableIndex === -1) { throw new Error( `The index of the VerifiableCredential in your current call can't be found in your previously submitted credentials. Are you trying to send a new Credential?\nverifiableCredential: ${vc}`, ); } - matches[index] = `$[${newIndex}]`; - }); - srm.name; - } - }); - } - private extractMatches(matchSubmissionRequirements: SubmissionRequirementMatch[]): string[] { - const matches: string[] = []; - matchSubmissionRequirements.forEach((e) => { - matches.push(...e.vc_path); - if (e.from_nested) { - matches.push(...this.extractMatches(e.from_nested)); - } - }); - return Array.from(new Set(matches)); - } + return `$.verifiableCredential[${selectableIndex}]`; + }), + }; + } - /** - * Since this is without SubmissionRequirements object, each InputDescriptor has to have at least one corresponding VerifiableCredential - * @param marked: info logs for `MarkForSubmissionEvaluation` handler - * @param pd - * @private - */ - private checkWithoutSubmissionRequirements(marked: HandlerCheckResult[], pd: IInternalPresentationDefinition): HandlerCheckResult[] { - const checkResult: HandlerCheckResult[] = []; - if (!(pd as InternalPresentationDefinitionV2).input_descriptors) { - return []; + if (match.from) { + return { + ...match, + input_descriptors: match.input_descriptors.map((inputDescriptor) => + this.remapVcPathsOnMatchToSelectableCredentialIndexes(inputDescriptor, selectableIndexes, originalVerifiableCredentials), + ), + }; } - if (!marked.length) { - return [ - { - input_descriptor_path: '', - evaluator: 'checkWithoutSubmissionRequirement', - verifiable_credential_path: '', - status: Status.ERROR, - payload: `Not all the InputDescriptors are addressed`, - }, - ]; + + if (match.from_nested) { + return { + ...match, + from_nested: match.from_nested.map((nested) => + this.remapVcPathsOnMatchToSelectableCredentialIndexes(nested, selectableIndexes, originalVerifiableCredentials), + ), + }; } - const inputDescriptors = (pd as InternalPresentationDefinitionV2).input_descriptors; - const markedInputDescriptorPaths: string[] = ObjectUtils.getDistinctFieldInObject(marked, 'input_descriptor_path') as string[]; - if (markedInputDescriptorPaths.length !== inputDescriptors.length) { - const inputDescriptorsFromLogs = ( - markedInputDescriptorPaths.map((value) => JsonPathUtils.extractInputField(pd, [value])[0].value) as InputDescriptorV2[] - ).map((value) => value.id); - for (let i = 0; i < (pd as InternalPresentationDefinitionV2).input_descriptors.length; i++) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (inputDescriptorsFromLogs.indexOf((pd as InternalPresentationDefinitionV2).input_descriptors[i].id) == -1) { - checkResult.push({ - input_descriptor_path: `$.input_descriptors[${i}]`, - evaluator: 'checkWithoutSubmissionRequirement', - verifiable_credential_path: '', - status: Status.ERROR, - payload: `Not all the InputDescriptors are addressed`, - }); - } + + throw new Error('Unsupported match type'); + } + + private extractVcPathsFromMatches(matches: SubmissionRequirementMatch[]): string[] { + const vcPaths: string[] = []; + + for (const match of matches) { + if (match.type === SubmissionRequirementMatchType.InputDescriptor) { + vcPaths.push(...match.vc_path); + } else if (match.from) { + vcPaths.push(...this.extractVcPathsFromMatches(match.input_descriptors)); + } else if (match.from_nested) { + vcPaths.push(...this.extractVcPathsFromMatches(match.from_nested)); } } - return checkResult; + return Array.from(new Set(vcPaths)); } - private matchSubmissionRequirements( + private getSubmissionRequirementMatchForSubmissionRequirement( pd: IInternalPresentationDefinition, - submissionRequirements: SubmissionRequirement[], - marked: HandlerCheckResult[], - ): SubmissionRequirementMatch[] { - const submissionRequirementMatches: SubmissionRequirementMatch[] = []; - for (const [srIndex, sr] of Object.entries(submissionRequirements)) { - // Create a default SubmissionRequirementMatch object - const srm: SubmissionRequirementMatch = { - rule: sr.rule, - vc_path: [], + sr: SubmissionRequirement, + srIndex: number, + idToVcMap: Map, + ): SubmissionRequirementMatchFrom | SubmissionRequirementMatchFromNested { + const rulePick = { + type: 'pick' as const, + min: sr.min, + max: sr.max, + count: sr.count, + }; + + if (sr.from) { + const from = sr.from; + const inputDescriptorsForGroup = pd.input_descriptors.filter((i) => i.group?.includes(from)); + + const inputDescriptorMatches = inputDescriptorsForGroup.map((inputDescriptor): SubmissionRequirementMatchInputDescriptor => { + const vcPaths = idToVcMap.get(inputDescriptor.id) ?? []; + return { + id: inputDescriptor.id, + name: inputDescriptor.name, + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: vcPaths, + areRequiredCredentialsPresent: vcPaths.length === 1 ? Status.INFO : vcPaths.length > 1 ? Status.WARN : Status.ERROR, + }; + }); + + const successInputDescriptors = inputDescriptorMatches.filter( + (match) => match.areRequiredCredentialsPresent === Status.INFO || match.areRequiredCredentialsPresent === Status.WARN, + ); + const hasWarning = successInputDescriptors.some((match) => match.areRequiredCredentialsPresent === Status.WARN); + const counStatus = this.determineCountStatus({ + rule: + sr.rule === 'all' + ? { + count: inputDescriptorsForGroup.length, + } + : sr, + hasCount: successInputDescriptors.length, + }); + return { + id: srIndex, name: sr.name, type: SubmissionRequirementMatchType.SubmissionRequirement, - id: Number(srIndex), - }; + from: sr.from, + areRequiredCredentialsPresent: counStatus === Status.INFO && hasWarning ? Status.WARN : counStatus, + rule: + sr.rule === 'pick' + ? rulePick + : { + type: sr.rule, + count: inputDescriptorsForGroup.length, + }, + input_descriptors: inputDescriptorMatches, + } satisfies SubmissionRequirementMatchFrom; + } - if (sr.from) { - srm.from = sr.from; - } - // Assign min, max, and count regardless of 'from' or 'from_nested' - sr.min ? (srm.min = sr.min) : undefined; - sr.max ? (srm.max = sr.max) : undefined; - sr.count ? (srm.count = sr.count) : undefined; + if (sr.from_nested) { + const nestedMatches = sr.from_nested.map((nsr, nsrIndex) => + this.getSubmissionRequirementMatchForSubmissionRequirement(pd, nsr, nsrIndex, idToVcMap), + ); - if (sr.from) { - const matchingVcPaths = this.getMatchingVcPathsForSubmissionRequirement(pd, sr, marked); - srm.vc_path.push(...matchingVcPaths); - submissionRequirementMatches.push(srm); - } else if (sr.from_nested) { - // Recursive call to matchSubmissionRequirements for nested requirements - try { - srm.from_nested = this.matchSubmissionRequirements(pd, sr.from_nested, marked); - submissionRequirementMatches.push(srm); - } catch (err) { - throw new Error(`Error in handling value of from_nested: ${sr.from_nested}: err: ${err}`); - } - } else { - // Throw an error if neither 'from' nor 'from_nested' is found - throw new Error("Invalid SubmissionRequirement object: Must contain either 'from' or 'from_nested'"); - } - } - return submissionRequirementMatches; - } + const successMatches = nestedMatches.filter( + (match) => match.areRequiredCredentialsPresent === Status.INFO || match.areRequiredCredentialsPresent === Status.WARN, + ); + const hasWarning = successMatches.some((match) => match.areRequiredCredentialsPresent === Status.WARN); + const counStatus = this.determineCountStatus({ + rule: + sr.rule === 'all' + ? { + count: nestedMatches.length, + } + : sr, + hasCount: successMatches.length, + }); - private matchWithoutSubmissionRequirements(marked: HandlerCheckResult[], pd: IInternalPresentationDefinition): SubmissionRequirementMatch[] { - const submissionRequirementMatches: SubmissionRequirementMatch[] = []; - const partitionedIdToVcMap: Map = this.createIdToVcMap(marked); - for (const [idPath, sameIdVcs] of partitionedIdToVcMap.entries()) { - if (!sameIdVcs || !sameIdVcs.length) { - continue; - } - for (const vcPath of sameIdVcs) { - const inputDescriptorResults = JsonPathUtils.extractInputField(pd, [idPath]); - if (inputDescriptorResults.length) { - const inputDescriptor = inputDescriptorResults[0].value; - submissionRequirementMatches.push({ - name: inputDescriptor.name || inputDescriptor.id, - rule: Rules.All, - vc_path: [vcPath], - - type: SubmissionRequirementMatchType.InputDescriptor, - id: inputDescriptor.id, - }); - } - } + return { + id: Number(srIndex), + areRequiredCredentialsPresent: counStatus === Status.INFO && hasWarning ? Status.WARN : counStatus, + name: sr.name, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from_nested: nestedMatches, + rule: + sr.rule === 'pick' + ? rulePick + : { + type: sr.rule, + count: sr.from_nested.length, + }, + } satisfies SubmissionRequirementMatchFromNested; } - return this.removeDuplicateSubmissionRequirementMatches(submissionRequirementMatches); + + // Throw an error if neither 'from' nor 'from_nested' is found + throw new Error("Invalid SubmissionRequirement object: Must contain either 'from' or 'from_nested'"); } - private getMatchingVcPathsForSubmissionRequirement( - pd: IInternalPresentationDefinition, - sr: SubmissionRequirement, - marked: HandlerCheckResult[], - ): string[] { - const vcPaths = new Set(); + private determineCountStatus({ rule, hasCount }: { hasCount: number; rule: { count?: number; min?: number; max?: number } }) { + const maxStatus = !rule.max || hasCount <= rule.max ? Status.INFO : Status.WARN; + const minStatus = !rule.min || hasCount >= rule.min ? Status.INFO : Status.ERROR; + const countStatus = !rule.count || rule.count === hasCount ? Status.INFO : hasCount < rule.count ? Status.ERROR : Status.WARN; + const allStatuses = [maxStatus, minStatus, countStatus]; - if (!sr.from) return Array.from(vcPaths); + return allStatuses.includes(Status.ERROR) ? Status.ERROR : allStatuses.includes(Status.WARN) ? Status.WARN : Status.INFO; + } - for (const m of marked) { - const inputDescriptor: InputDescriptorV2 = jp.query(pd, m.input_descriptor_path)[0]; - if (inputDescriptor.group && inputDescriptor.group.indexOf(sr.from) === -1) { - continue; - } - if (m.payload.group.includes(sr.from)) { - vcPaths.add(m.verifiable_credential_path); - } + private matchPresentationDefinition(marked: HandlerCheckResult[], pd: IInternalPresentationDefinition): SubmissionRequirementMatch[] { + const idToVcMap = this.createInputDescriptorIdToVcMap(marked, pd); + + if (pd.submission_requirements) { + return pd.submission_requirements.map((sr, srIndex) => this.getSubmissionRequirementMatchForSubmissionRequirement(pd, sr, srIndex, idToVcMap)); } - return Array.from(vcPaths); + return pd.input_descriptors.map((inputDescriptor): SubmissionRequirementMatchInputDescriptor => { + const vcPaths = idToVcMap.get(inputDescriptor.id) ?? []; + return { + areRequiredCredentialsPresent: vcPaths.length === 1 ? Status.INFO : vcPaths.length > 1 ? Status.WARN : Status.ERROR, + id: inputDescriptor.id, + name: inputDescriptor.name, + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: vcPaths, + }; + }); } public evaluate( @@ -1138,141 +1115,24 @@ export class EvaluationClientWrapper { } } - private removeDuplicateSubmissionRequirementMatches(matches: SubmissionRequirementMatch[]) { - return matches.filter((match, index) => { - const _match = JSON.stringify(match); - return ( - index === - matches.findIndex((obj) => { - return JSON.stringify(obj) === _match; - }) - ); - }); - } - - public fillSelectableCredentialsToVerifiableCredentialsMapping(selectResults: SelectResults, wrappedVcs: WrappedVerifiableCredential[]) { - if (selectResults) { - selectResults.verifiableCredential?.forEach((selectableCredential) => { - const foundIndex = wrappedVcs.findIndex((wrappedVc) => - CredentialMapper.areOriginalVerifiableCredentialsEqual(wrappedVc.original, selectableCredential), - ); - - if (foundIndex === -1) { - throw new Error('index is not right'); - } - selectResults.vcIndexes - ? !selectResults.vcIndexes.includes(foundIndex) && selectResults.vcIndexes.push(foundIndex) - : (selectResults.vcIndexes = [foundIndex]); - }); - } - } - - public determineAreRequiredCredentialsPresent( - presentationDefinition: IInternalPresentationDefinition, - matchSubmissionRequirements: SubmissionRequirementMatch[] | undefined, - parentMsr?: SubmissionRequirementMatch, - ): Status { + public determineAreRequiredCredentialsPresent(matchSubmissionRequirements: SubmissionRequirementMatch[] | undefined): Status { if (!matchSubmissionRequirements || !matchSubmissionRequirements.length) { return Status.ERROR; } - // collect child statuses - const childStatuses = matchSubmissionRequirements.map((m) => this.determineSubmissionRequirementStatus(presentationDefinition, m)); + const statuses = matchSubmissionRequirements.map((match) => match.areRequiredCredentialsPresent); - // decide status based on child statuses and parent's rule - if (!parentMsr) { - if (childStatuses.includes(Status.ERROR)) { - return Status.ERROR; - } else if (childStatuses.includes(Status.WARN)) { - return Status.WARN; - } else { - return Status.INFO; - } - } else { - if (parentMsr.rule === Rules.All && childStatuses.includes(Status.ERROR)) { - return Status.ERROR; - } - - const nonErrStatCount = childStatuses.filter((status) => status !== Status.ERROR).length; - - if (parentMsr.count) { - return parentMsr.count > nonErrStatCount ? Status.ERROR : parentMsr.count < nonErrStatCount ? Status.WARN : Status.INFO; - } else { - if (parentMsr.min && parentMsr.min > nonErrStatCount) { - return Status.ERROR; - } else if (parentMsr.max && parentMsr.max < nonErrStatCount) { - return Status.WARN; - } - } - } - - return Status.INFO; - } - - private determineSubmissionRequirementStatus(pd: IInternalPresentationDefinition, m: SubmissionRequirementMatch): Status { - if (m.from && m.from_nested) { - throw new Error('Invalid submission_requirement object: MUST contain either a from or from_nested property.'); - } - - if (!m.from && !m.from_nested && m.vc_path.length !== 1) { - return Status.ERROR; - } - - if (m.from) { - const groupCount = this.countGroupIDs((pd as InternalPresentationDefinitionV2).input_descriptors, m.from); - switch (m.rule) { - case Rules.All: - // Ensure that all descriptors associated with `m.from` are satisfied. - return m.vc_path.length === groupCount ? Status.INFO : Status.WARN; - case Rules.Pick: - return this.getPickRuleStatus(m); - default: - return Status.ERROR; - } - } else if (m.from_nested) { - return this.determineAreRequiredCredentialsPresent(pd, m.from_nested, m); - } - - return Status.INFO; - } - - private getPickRuleStatus(m: SubmissionRequirementMatch): Status { - if (m.vc_path.length === 0) { + if (statuses.includes(Status.ERROR)) { return Status.ERROR; } - if (m.count && m.vc_path.length !== m.count) { - return m.vc_path.length > m.count ? Status.WARN : Status.ERROR; - } - - if (m.min && m.vc_path.length < m.min) { - return Status.ERROR; - } - - if (m.max && m.vc_path.length > m.max) { + if (statuses.includes(Status.WARN)) { return Status.WARN; } return Status.INFO; } - private updateSubmissionRequirementMatchPathToAlias(submissionRequirementMatch: SubmissionRequirementMatch, alias: string) { - const vc_path: string[] = []; - submissionRequirementMatch.vc_path.forEach((m) => { - if (m.startsWith(`$.${alias}`)) { - vc_path.push(m); - } else { - vc_path.push(m.replace('$', `$.${alias}`)); - } - }); - submissionRequirementMatch.vc_path = vc_path; - if (submissionRequirementMatch.from_nested) { - submissionRequirementMatch.from_nested.forEach((f) => { - this.updateSubmissionRequirementMatchPathToAlias(f, alias); - }); - } - } - private updatePresentationSubmissionPathToVpPath(presentationSubmission?: PresentationSubmission) { const descriptorMap = presentationSubmission ? presentationSubmission.descriptor_map @@ -1303,9 +1163,7 @@ export class EvaluationClientWrapper { } } - private createIdToVcMap(marked: HandlerCheckResult[]): Map { - const partitionedResults: Map = new Map(); - + private createInputDescriptorIdToVcMap(marked: HandlerCheckResult[], pd: IInternalPresentationDefinition): Map { const partitionedBasedOnId: Map = new Map(); for (let i = 0; i < marked.length; i++) { const currentIdPath: string = marked[i].input_descriptor_path; @@ -1319,6 +1177,7 @@ export class EvaluationClientWrapper { } } + const partitionedResults = new Map(); for (const [idPath, sameVcCheckResults] of partitionedBasedOnId.entries()) { const vcPaths: string[] = []; for (let i = 0; i < sameVcCheckResults.length; i++) { @@ -1326,18 +1185,15 @@ export class EvaluationClientWrapper { vcPaths.push(sameVcCheckResults[i].verifiable_credential_path); } } - partitionedResults.set(idPath, vcPaths); - } - return partitionedResults; - } - private countGroupIDs(input_descriptors: Array, from: string): number { - let count = 0; - for (const descriptor of input_descriptors) { - if (descriptor.group && descriptor.group.includes(from)) { - count++; + const [inputDescriptorResult] = JsonPathUtils.extractInputField(pd, [idPath]); + if (!inputDescriptorResult) { + throw new Error(`Unable to find input descriptor with path ${idPath} in presentation definition`); } + + partitionedResults.set(inputDescriptorResult.value.id, vcPaths); } - return count; + + return partitionedResults; } } diff --git a/lib/types/Internal.types.ts b/lib/types/Internal.types.ts index 9ecb2256..2bef28d0 100644 --- a/lib/types/Internal.types.ts +++ b/lib/types/Internal.types.ts @@ -22,7 +22,7 @@ export interface IInternalPresentationDefinition { name?: string; purpose?: string; submission_requirements?: Array; - input_descriptors: Array<{ id: string; group?: string[] }>; + input_descriptors: Array<{ id: string; group?: string[]; name?: string }>; getVersion(): PEVersion; } diff --git a/test/Mdoc.spec.ts b/test/Mdoc.spec.ts index 7129350a..749c46ac 100644 --- a/test/Mdoc.spec.ts +++ b/test/Mdoc.spec.ts @@ -97,8 +97,7 @@ describe('evaluate mdoc', () => { expect(result.errors?.length).toEqual(0); expect(result.matches).toEqual([ { - name: 'org.eu.university', - rule: 'all', + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[0]'], type: SubmissionRequirementMatchType.InputDescriptor, id: 'org.eu.university', @@ -118,16 +117,14 @@ describe('evaluate mdoc', () => { expect(result.errors?.length).toEqual(0); expect(result.matches).toEqual([ { - name: 'org.eu.university', - rule: 'all', + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[0]'], type: SubmissionRequirementMatchType.InputDescriptor, id: 'org.eu.university', }, { id: 'OpenBadgeCredentialDescriptor', - name: 'OpenBadgeCredentialDescriptor', - rule: 'all', + areRequiredCredentialsPresent: 'info', type: SubmissionRequirementMatchType.InputDescriptor, vc_path: ['$.verifiableCredential[1]'], }, diff --git a/test/PEX.spec.ts b/test/PEX.spec.ts index dd5737af..fc0453b7 100644 --- a/test/PEX.spec.ts +++ b/test/PEX.spec.ts @@ -1425,21 +1425,21 @@ describe('evaluate', () => { expect(result.matches).toEqual([ { name: 'Residence permit date of birth and photo', - rule: 'all', + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[0]'], type: 'InputDescriptor', id: 'ResidencePermit', }, { name: 'ID date of birth and photo', - rule: 'all', + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[1]'], type: 'InputDescriptor', id: 'IDDoB', }, { name: 'Driving licence date of birth and photo', - rule: 'all', + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[2]'], type: 'InputDescriptor', id: 'DrivingLicenceDoB', @@ -1530,22 +1530,58 @@ describe('evaluate', () => { expect(result.matches).toEqual([ { - rule: 'pick', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]'], + id: 0, name: 'Proof of age and photo', type: 'SubmissionRequirement', - id: 0, from: 'validAgeCheckInputDescriptor', - count: 1, + areRequiredCredentialsPresent: 'warn', + rule: { + type: 'pick', + count: 1, + }, + input_descriptors: [ + { + id: 'IDDoB', + name: 'ID date of birth and photo', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: 'info', + }, + { + id: 'DrivingLicenceDoB', + name: 'Driving licence date of birth and photo', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: 'info', + }, + ], }, { - rule: 'pick', - vc_path: ['$.verifiableCredential[2]', '$.verifiableCredential[0]'], + id: 1, name: 'Proof of other', type: 'SubmissionRequirement', - id: 1, from: 'validOtherCheck', - count: 1, + areRequiredCredentialsPresent: 'warn', + rule: { + type: 'pick', + count: 1, + }, + input_descriptors: [ + { + id: 'ResidencePermit', + name: 'Residence permit date of birth and photo', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: 'info', + }, + { + id: 'IDDoB', + name: 'ID date of birth and photo', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: 'info', + }, + ], }, ]); }); diff --git a/test/PEXv2.spec.ts b/test/PEXv2.spec.ts index 7eabb257..32ff109d 100644 --- a/test/PEXv2.spec.ts +++ b/test/PEXv2.spec.ts @@ -314,11 +314,11 @@ describe('evaluate', () => { expect(JSON.stringify(result!.matches)).toBe( JSON.stringify([ { + areRequiredCredentialsPresent: 'info', + id: 'drivers_license_information', name: 'Verify Valid License', - rule: 'all', - vc_path: ['$.verifiableCredential[0]'], type: 'InputDescriptor', - id: 'drivers_license_information', + vc_path: ['$.verifiableCredential[0]'], }, ]), ); diff --git a/test/SdJwt.spec.ts b/test/SdJwt.spec.ts index 68fc268a..88818fe4 100644 --- a/test/SdJwt.spec.ts +++ b/test/SdJwt.spec.ts @@ -113,10 +113,88 @@ const decodedSdJwtVcWithDisclosuresRemoved = { signedPayload: decodedSdJwtVc.signedPayload, } satisfies SdJwtDecodedVerifiableCredential; +const pidSdJwt = + 'eyJ4NWMiOlsiTUlJQ2REQ0NBaHVnQXdJQkFnSUJBakFLQmdncWhrak9QUVFEQWpDQmlERUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneEVUQVBCZ05WQkFzTUNGUWdRMU1nU1VSRk1UWXdOQVlEVlFRRERDMVRVRkpKVGtRZ1JuVnVhMlVnUlZWRVNTQlhZV3hzWlhRZ1VISnZkRzkwZVhCbElFbHpjM1ZwYm1jZ1EwRXdIaGNOTWpRd05UTXhNRGd4TXpFM1doY05NalV3TnpBMU1EZ3hNekUzV2pCc01Rc3dDUVlEVlFRR0V3SkVSVEVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hNakF3QmdOVkJBTU1LVk5RVWtsT1JDQkdkVzVyWlNCRlZVUkpJRmRoYkd4bGRDQlFjbTkwYjNSNWNHVWdTWE56ZFdWeU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU9GQnE0WU1LZzR3NWZUaWZzeXR3QnVKZi83RTdWaFJQWGlObTUyUzNxMUVUSWdCZFh5REsza1Z4R3hnZUhQaXZMUDN1dU12UzZpREVjN3FNeG12ZHVLT0JrRENCalRBZEJnTlZIUTRFRmdRVWlQaENrTEVyRFhQTFcyL0owV1ZlZ2h5dyttSXdEQVlEVlIwVEFRSC9CQUl3QURBT0JnTlZIUThCQWY4RUJBTUNCNEF3TFFZRFZSMFJCQ1l3SklJaVpHVnRieTV3YVdRdGFYTnpkV1Z5TG1KMWJtUmxjMlJ5ZFdOclpYSmxhUzVrWlRBZkJnTlZIU01FR0RBV2dCVFVWaGpBaVRqb0RsaUVHTWwyWXIrcnU4V1F2akFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUFiZjVUemtjUXpoZldvSW95aTFWTjdkOEk5QnNGS20xTVdsdVJwaDJieUdRSWdLWWtkck5mMnhYUGpWU2JqVy9VLzVTNXZBRUM1WHhjT2FudXNPQnJvQmJVPSIsIk1JSUNlVENDQWlDZ0F3SUJBZ0lVQjVFOVFWWnRtVVljRHRDaktCL0gzVlF2NzJnd0NnWUlLb1pJemowRUF3SXdnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUI0WERUSTBNRFV6TVRBMk5EZ3dPVm9YRFRNME1EVXlPVEEyTkRnd09Wb3dnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFWUd6ZHdGRG5jNytLbjVpYkF2Q09NOGtlNzdWUXhxZk1jd1pMOElhSUErV0NST2NDZm1ZL2dpSDkycU1ydTVwL2t5T2l2RTBSQy9JYmRNT052RG9VeWFObU1HUXdIUVlEVlIwT0JCWUVGTlJXR01DSk9PZ09XSVFZeVhaaXY2dTd4WkMrTUI4R0ExVWRJd1FZTUJhQUZOUldHTUNKT09nT1dJUVl5WFppdjZ1N3haQytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnR0dNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJR0VtN3drWktIdC9hdGI0TWRGblhXNnlybndNVVQydTEzNmdkdGwxMFk2aEFpQnVURnF2Vll0aDFyYnh6Q1AweFdaSG1RSzlrVnl4bjhHUGZYMjdFSXp6c3c9PSJdLCJraWQiOiJNSUdVTUlHT3BJR0xNSUdJTVFzd0NRWURWUVFHRXdKRVJURVBNQTBHQTFVRUJ3d0dRbVZ5YkdsdU1SMHdHd1lEVlFRS0RCUkNkVzVrWlhOa2NuVmphMlZ5WldrZ1IyMWlTREVSTUE4R0ExVUVDd3dJVkNCRFV5QkpSRVV4TmpBMEJnTlZCQU1NTFZOUVVrbE9SQ0JHZFc1clpTQkZWVVJKSUZkaGJHeGxkQ0JRY205MGIzUjVjR1VnU1hOemRXbHVaeUJEUVFJQkFnPT0iLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiVS01ZlVXLU5EM1laajZTcUdyQXV4NXJWYWZOalhqZ2hvMmRUUmpQX3hOTSJdfSwiX3NkIjpbIjlFaUpQNEw2NDI0bEtTVGs5NHpIOWhaWVc5UjNuS1R3V0V5TVBJN2dvWHciLCJHVlhRWEtFMmpWR1d0VEF6T1d5ck85TTZySW1qYkZJWGFnRkMyWElMbGhJIiwiUUV2bHpNd0ozZS1tOEtpWEk5bGx2bnVQblh5UHRXN2VCSF9GcXFVTnk3WSIsImljWkpTRkFqLVg3T29Sam5vRFRReXFwU1dNQUVuaTcydWZDZmFFWC1uQkUiLCJsUHJqb3BqbEN5bFdHWVo0cmh4S1RUTUsxS3p1Sm5ISUtybzNwUUhlUXF3IiwicjJORHZtRFY3QmU3TlptVFR0VE9fekdZX3RTdWdYVXoxeDJBXzZuOFhvdyIsInJPbjFJUkpUQWtEV1pSTGc3MUYzaDVsbFpPc1ZPMl9aemlOUy1majNEUFUiXSwiYWRkcmVzcyI6eyJfc2QiOlsiQnI1aVZtZnZlaTloQ01mMktVOGRFVjFER2hrdUtsQ1pUeGFEQ0FMb3NJbyIsIkx6czJpR09SNHF0clhhYmdwMzFfcjFFUFNmazlaUDJQRElJUTRQaHlPT00iLCJadUV5cG41Y0s0WVpWdHdkeGFoWXJqMjZ1MFI2UmxpOVVJWlNjUGhoWTB3Iiwidi1rMzl2VGI5NFI5a25VWTZtbzlXUVdEQkNJS3lya0J4bExTQVl3T2MyNCJdfSwiaXNzdWluZ19jb3VudHJ5IjoiREUiLCJ2Y3QiOiJodHRwczovL2V4YW1wbGUuYm1pLmJ1bmQuZGUvY3JlZGVudGlhbC9waWQvMS4wIiwiaXNzdWluZ19hdXRob3JpdHkiOiJERSIsIl9zZF9hbGciOiJzaGEtMjU2IiwiaXNzIjoiaHR0cHM6Ly9kZW1vLnBpZC1pc3N1ZXIuYnVuZGVzZHJ1Y2tlcmVpLmRlL2MxIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IkhzS194Tl95SVU4eWlqdW9BWlhsbndFRU00ZlhZenVNRmd5TTE5SmRYMUkiLCJ5IjoiQUR2NnplVDl3YmgxU0ZxMG14TkcxMUZueC05eFdSRFcwR18xN1dSRXpRSSJ9fSwiZXhwIjoxNzMzNTcxMzI3LCJpYXQiOjE3MzIzNjE3MjcsImFnZV9lcXVhbF9vcl9vdmVyIjp7Il9zZCI6WyJLRDF0U0hnYWotZi1qbkZURkRDMW1sZ0RwNzhMZE1KcHlqWnRRU0k4a1ZnIiwiTDRjTTMtZU1mRHg0Znc2UEw3OVRTVFBnM042VXdzOGNPc3JOYmNqaEEtYyIsImRYUFBQX2lmNFM3XzBzcXZXNTBwZEdlMWszbS1wMnM3M1JicDlncThGaDAiLCJtYnllcU05YUkzRkVvWmFoODA5eTN0dlRCV1NvZTBMSlRUYTlONGNjdmlZIiwicm1zd0dEZnhvS0ZFYlFsNzZ4S1ZVT0hrX0MyQlVpVnQ5RDlvMTFrMmZNSSIsInZsY2Y4WTNhQnNTeEZBeVZfYk9NTndvX3FTT1pHc3ViSVZiY0FVSWVBSGMiXX19.gruqjNOuJBgHXEnG9e60wOoqiyEaL1K9pdL215a0ffZCjtIZ_kICDrO5vBiTrEmvjjd6w_N_thEYLhzob77Epg~WyJWRXlWQWF0LXoyNU8tbkQ0MVBaOGdnIiwiZmFtaWx5X25hbWUiLCJNVVNURVJNQU5OIl0~WyJLcnRPei1lRk9hMU9JYmpmUHUxcHRBIiwiZ2l2ZW5fbmFtZSIsIkVSSUtBIl0~WyJQQUVjSHp0NWk5bFFzNUZlRmFGUS1RIiwiYmlydGhkYXRlIiwiMTk2NC0wOC0xMiJd~'; + const pex = new PEX({ hasher, }); +function getPresentationDefinitionV2PidAndMdl(): PresentationDefinitionV2 { + return { + id: '1ad8ea6e-ec51-4e14-b316-dd76a6275480', + name: 'PID and MDL - Rent a Car (vc+sd-jwt)', + purpose: 'To secure your car reservations and finalize the transaction, we require the following attributes', + input_descriptors: [ + { + id: 'bf8669f4-0cf3-4d16-b72b-b47eb702a7cd', + format: { + 'vc+sd-jwt': { + 'sd-jwt_alg_values': ['ES256'], + 'kb-jwt_alg_values': ['ES256'], + }, + }, + group: ['A'], + constraints: { + limit_disclosure: 'required', + fields: [ + { path: ['$.document_number'] }, + { path: ['$.portrait'] }, + { path: ['$.issue_date'] }, + { path: ['$.expiry_date'] }, + { path: ['$.issuing_country'] }, + { path: ['$.issuing_authority'] }, + { path: ['$.driving_priviliges'] }, + { + path: ['$.vct'], + filter: { + type: 'string', + enum: ['https://example.eudi.ec.europa.eu/mdl/1'], + }, + }, + ], + }, + }, + { + id: '99fce09b-a0d3-415b-b8a7-3eab8829babc', + format: { + 'vc+sd-jwt': { + 'sd-jwt_alg_values': ['ES256'], + 'kb-jwt_alg_values': ['ES256'], + }, + }, + group: ['B'], + constraints: { + limit_disclosure: 'required', + fields: [ + { path: ['$.given_name'] }, + { path: ['$.family_name'] }, + { path: ['$.birthdate'] }, + { + path: ['$.vct'], + filter: { + type: 'string', + enum: ['https://example.bmi.bund.de/credential/pid/1.0', 'urn:eu.europa.ec.eudi:pid:1'], + }, + }, + { + path: ['$.iss'], + filter: { + type: 'string', + enum: [ + 'https://demo.pid-issuer.bundesdruckerei.de/c', + 'https://demo.pid-issuer.bundesdruckerei.de/c1', + 'https://demo.pid-issuer.bundesdruckerei.de/b1', + ], + }, + }, + ], + }, + }, + ], + }; +} + function getPresentationDefinitionV2(): PresentationDefinitionV2 { return { id: '32f54163-7166-48f1-93d8-ff217bdb0653', @@ -182,7 +260,7 @@ describe('evaluate', () => { expect(result.matches).toEqual([ { name: 'Washington State Business License', - rule: 'all', + areRequiredCredentialsPresent: Status.INFO, vc_path: ['$.verifiableCredential[0]'], type: SubmissionRequirementMatchType.InputDescriptor, id: 'wa_driver_license', @@ -200,7 +278,7 @@ describe('evaluate', () => { expect(result.matches).toEqual([ { name: 'Washington State Business License', - rule: 'all', + areRequiredCredentialsPresent: Status.INFO, vc_path: ['$.verifiableCredential[0]'], type: SubmissionRequirementMatchType.InputDescriptor, id: 'wa_driver_license', @@ -309,4 +387,448 @@ describe('evaluate', () => { value: presentationResult.presentationSubmission, }); }); + + it('select where only one of requested credentials is present', async () => { + const presentationDefinition = getPresentationDefinitionV2PidAndMdl(); + const selectResults = pex.selectFrom(presentationDefinition, [pidSdJwt]); + + expect(selectResults).toEqual({ + errors: [ + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate failed filter evaluation: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'MarkForSubmissionEvaluation', + status: 'error', + message: 'The input candidate is not eligible for submission: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + ], + matches: [ + { + areRequiredCredentialsPresent: 'error', + id: 'bf8669f4-0cf3-4d16-b72b-b47eb702a7cd', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: [], + }, + { + areRequiredCredentialsPresent: Status.INFO, + id: '99fce09b-a0d3-415b-b8a7-3eab8829babc', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + }, + ], + areRequiredCredentialsPresent: 'error', + verifiableCredential: [ + 'eyJ4NWMiOlsiTUlJQ2REQ0NBaHVnQXdJQkFnSUJBakFLQmdncWhrak9QUVFEQWpDQmlERUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneEVUQVBCZ05WQkFzTUNGUWdRMU1nU1VSRk1UWXdOQVlEVlFRRERDMVRVRkpKVGtRZ1JuVnVhMlVnUlZWRVNTQlhZV3hzWlhRZ1VISnZkRzkwZVhCbElFbHpjM1ZwYm1jZ1EwRXdIaGNOTWpRd05UTXhNRGd4TXpFM1doY05NalV3TnpBMU1EZ3hNekUzV2pCc01Rc3dDUVlEVlFRR0V3SkVSVEVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hNakF3QmdOVkJBTU1LVk5RVWtsT1JDQkdkVzVyWlNCRlZVUkpJRmRoYkd4bGRDQlFjbTkwYjNSNWNHVWdTWE56ZFdWeU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU9GQnE0WU1LZzR3NWZUaWZzeXR3QnVKZi83RTdWaFJQWGlObTUyUzNxMUVUSWdCZFh5REsza1Z4R3hnZUhQaXZMUDN1dU12UzZpREVjN3FNeG12ZHVLT0JrRENCalRBZEJnTlZIUTRFRmdRVWlQaENrTEVyRFhQTFcyL0owV1ZlZ2h5dyttSXdEQVlEVlIwVEFRSC9CQUl3QURBT0JnTlZIUThCQWY4RUJBTUNCNEF3TFFZRFZSMFJCQ1l3SklJaVpHVnRieTV3YVdRdGFYTnpkV1Z5TG1KMWJtUmxjMlJ5ZFdOclpYSmxhUzVrWlRBZkJnTlZIU01FR0RBV2dCVFVWaGpBaVRqb0RsaUVHTWwyWXIrcnU4V1F2akFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUFiZjVUemtjUXpoZldvSW95aTFWTjdkOEk5QnNGS20xTVdsdVJwaDJieUdRSWdLWWtkck5mMnhYUGpWU2JqVy9VLzVTNXZBRUM1WHhjT2FudXNPQnJvQmJVPSIsIk1JSUNlVENDQWlDZ0F3SUJBZ0lVQjVFOVFWWnRtVVljRHRDaktCL0gzVlF2NzJnd0NnWUlLb1pJemowRUF3SXdnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUI0WERUSTBNRFV6TVRBMk5EZ3dPVm9YRFRNME1EVXlPVEEyTkRnd09Wb3dnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFWUd6ZHdGRG5jNytLbjVpYkF2Q09NOGtlNzdWUXhxZk1jd1pMOElhSUErV0NST2NDZm1ZL2dpSDkycU1ydTVwL2t5T2l2RTBSQy9JYmRNT052RG9VeWFObU1HUXdIUVlEVlIwT0JCWUVGTlJXR01DSk9PZ09XSVFZeVhaaXY2dTd4WkMrTUI4R0ExVWRJd1FZTUJhQUZOUldHTUNKT09nT1dJUVl5WFppdjZ1N3haQytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnR0dNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJR0VtN3drWktIdC9hdGI0TWRGblhXNnlybndNVVQydTEzNmdkdGwxMFk2aEFpQnVURnF2Vll0aDFyYnh6Q1AweFdaSG1RSzlrVnl4bjhHUGZYMjdFSXp6c3c9PSJdLCJraWQiOiJNSUdVTUlHT3BJR0xNSUdJTVFzd0NRWURWUVFHRXdKRVJURVBNQTBHQTFVRUJ3d0dRbVZ5YkdsdU1SMHdHd1lEVlFRS0RCUkNkVzVrWlhOa2NuVmphMlZ5WldrZ1IyMWlTREVSTUE4R0ExVUVDd3dJVkNCRFV5QkpSRVV4TmpBMEJnTlZCQU1NTFZOUVVrbE9SQ0JHZFc1clpTQkZWVVJKSUZkaGJHeGxkQ0JRY205MGIzUjVjR1VnU1hOemRXbHVaeUJEUVFJQkFnPT0iLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiVS01ZlVXLU5EM1laajZTcUdyQXV4NXJWYWZOalhqZ2hvMmRUUmpQX3hOTSJdfSwiX3NkIjpbIjlFaUpQNEw2NDI0bEtTVGs5NHpIOWhaWVc5UjNuS1R3V0V5TVBJN2dvWHciLCJHVlhRWEtFMmpWR1d0VEF6T1d5ck85TTZySW1qYkZJWGFnRkMyWElMbGhJIiwiUUV2bHpNd0ozZS1tOEtpWEk5bGx2bnVQblh5UHRXN2VCSF9GcXFVTnk3WSIsImljWkpTRkFqLVg3T29Sam5vRFRReXFwU1dNQUVuaTcydWZDZmFFWC1uQkUiLCJsUHJqb3BqbEN5bFdHWVo0cmh4S1RUTUsxS3p1Sm5ISUtybzNwUUhlUXF3IiwicjJORHZtRFY3QmU3TlptVFR0VE9fekdZX3RTdWdYVXoxeDJBXzZuOFhvdyIsInJPbjFJUkpUQWtEV1pSTGc3MUYzaDVsbFpPc1ZPMl9aemlOUy1majNEUFUiXSwiYWRkcmVzcyI6eyJfc2QiOlsiQnI1aVZtZnZlaTloQ01mMktVOGRFVjFER2hrdUtsQ1pUeGFEQ0FMb3NJbyIsIkx6czJpR09SNHF0clhhYmdwMzFfcjFFUFNmazlaUDJQRElJUTRQaHlPT00iLCJadUV5cG41Y0s0WVpWdHdkeGFoWXJqMjZ1MFI2UmxpOVVJWlNjUGhoWTB3Iiwidi1rMzl2VGI5NFI5a25VWTZtbzlXUVdEQkNJS3lya0J4bExTQVl3T2MyNCJdfSwiaXNzdWluZ19jb3VudHJ5IjoiREUiLCJ2Y3QiOiJodHRwczovL2V4YW1wbGUuYm1pLmJ1bmQuZGUvY3JlZGVudGlhbC9waWQvMS4wIiwiaXNzdWluZ19hdXRob3JpdHkiOiJERSIsIl9zZF9hbGciOiJzaGEtMjU2IiwiaXNzIjoiaHR0cHM6Ly9kZW1vLnBpZC1pc3N1ZXIuYnVuZGVzZHJ1Y2tlcmVpLmRlL2MxIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IkhzS194Tl95SVU4eWlqdW9BWlhsbndFRU00ZlhZenVNRmd5TTE5SmRYMUkiLCJ5IjoiQUR2NnplVDl3YmgxU0ZxMG14TkcxMUZueC05eFdSRFcwR18xN1dSRXpRSSJ9fSwiZXhwIjoxNzMzNTcxMzI3LCJpYXQiOjE3MzIzNjE3MjcsImFnZV9lcXVhbF9vcl9vdmVyIjp7Il9zZCI6WyJLRDF0U0hnYWotZi1qbkZURkRDMW1sZ0RwNzhMZE1KcHlqWnRRU0k4a1ZnIiwiTDRjTTMtZU1mRHg0Znc2UEw3OVRTVFBnM042VXdzOGNPc3JOYmNqaEEtYyIsImRYUFBQX2lmNFM3XzBzcXZXNTBwZEdlMWszbS1wMnM3M1JicDlncThGaDAiLCJtYnllcU05YUkzRkVvWmFoODA5eTN0dlRCV1NvZTBMSlRUYTlONGNjdmlZIiwicm1zd0dEZnhvS0ZFYlFsNzZ4S1ZVT0hrX0MyQlVpVnQ5RDlvMTFrMmZNSSIsInZsY2Y4WTNhQnNTeEZBeVZfYk9NTndvX3FTT1pHc3ViSVZiY0FVSWVBSGMiXX19.gruqjNOuJBgHXEnG9e60wOoqiyEaL1K9pdL215a0ffZCjtIZ_kICDrO5vBiTrEmvjjd6w_N_thEYLhzob77Epg~WyJWRXlWQWF0LXoyNU8tbkQ0MVBaOGdnIiwiZmFtaWx5X25hbWUiLCJNVVNURVJNQU5OIl0~WyJLcnRPei1lRk9hMU9JYmpmUHUxcHRBIiwiZ2l2ZW5fbmFtZSIsIkVSSUtBIl0~WyJQQUVjSHp0NWk5bFFzNUZlRmFGUS1RIiwiYmlydGhkYXRlIiwiMTk2NC0wOC0xMiJd~', + ], + warnings: [], + vcIndexes: [0], + }); + }); + + it('select where only one of requested credentials is present with submission requirements', async () => { + const presentationDefinition = getPresentationDefinitionV2PidAndMdl(); + presentationDefinition.submission_requirements = [ + { + rule: 'all', + from: 'A', + }, + { + rule: 'pick', + count: 1, + from: 'B', + }, + ]; + const selectResults = pex.selectFrom(presentationDefinition, [pidSdJwt]); + expect(selectResults).toEqual({ + errors: [ + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate failed filter evaluation: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'MarkForSubmissionEvaluation', + status: 'error', + message: 'The input candidate is not eligible for submission: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + ], + matches: [ + { + id: 0, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: 'error', + rule: { + type: 'all', + count: 1, + }, + input_descriptors: [ + { + id: 'bf8669f4-0cf3-4d16-b72b-b47eb702a7cd', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: [], + areRequiredCredentialsPresent: 'error', + }, + ], + }, + { + id: 1, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'pick', + count: 1, + }, + input_descriptors: [ + { + id: '99fce09b-a0d3-415b-b8a7-3eab8829babc', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], + }, + ], + areRequiredCredentialsPresent: 'error', + verifiableCredential: [pidSdJwt], + warnings: [], + vcIndexes: [0], + }); + }); + + it('select where only one of requested credentials is present with nested submission requirements', async () => { + const presentationDefinition = getPresentationDefinitionV2PidAndMdl(); + presentationDefinition.submission_requirements = [ + { + rule: 'pick', + count: 2, + from_nested: [ + { + from: 'A', + rule: 'all', + }, + { + rule: 'all', + from: 'B', + }, + ], + }, + { + rule: 'pick', + count: 1, + from_nested: [ + { + from: 'A', + rule: 'all', + }, + { + rule: 'all', + from: 'B', + }, + ], + }, + { + rule: 'all', + from_nested: [ + { + from: 'A', + rule: 'pick', + count: 1, + }, + { + rule: 'pick', + from: 'B', + count: 1, + }, + ], + }, + { + rule: 'all', + from: 'B', + }, + { + rule: 'all', + from: 'A', + }, + ]; + const selectResults = pex.selectFrom(presentationDefinition, [pidSdJwt]); + expect(selectResults).toEqual({ + errors: [ + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate does not contain property: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'FilterEvaluation', + status: 'error', + message: 'Input candidate failed filter evaluation: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + { + tag: 'MarkForSubmissionEvaluation', + status: 'error', + message: 'The input candidate is not eligible for submission: $.input_descriptors[0]: $.verifiableCredential[0]', + }, + ], + matches: [ + { + id: 0, + areRequiredCredentialsPresent: 'error', + type: SubmissionRequirementMatchType.SubmissionRequirement, + from_nested: [ + { + id: 0, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: 'error', + rule: { + type: 'all', + count: 1, + }, + input_descriptors: [ + { + id: 'bf8669f4-0cf3-4d16-b72b-b47eb702a7cd', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: [], + areRequiredCredentialsPresent: 'error', + }, + ], + }, + { + id: 1, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'all', + count: 1, + }, + input_descriptors: [ + { + id: '99fce09b-a0d3-415b-b8a7-3eab8829babc', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], + }, + ], + rule: { + type: 'pick', + count: 2, + }, + }, + { + id: 1, + areRequiredCredentialsPresent: Status.INFO, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from_nested: [ + { + id: 0, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: 'error', + rule: { + type: 'all', + count: 1, + }, + input_descriptors: [ + { + id: 'bf8669f4-0cf3-4d16-b72b-b47eb702a7cd', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: [], + areRequiredCredentialsPresent: 'error', + }, + ], + }, + { + id: 1, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'all', + count: 1, + }, + input_descriptors: [ + { + id: '99fce09b-a0d3-415b-b8a7-3eab8829babc', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], + }, + ], + rule: { + type: 'pick', + count: 1, + }, + }, + { + id: 2, + areRequiredCredentialsPresent: 'error', + type: SubmissionRequirementMatchType.SubmissionRequirement, + from_nested: [ + { + id: 0, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: 'error', + rule: { + type: 'pick', + count: 1, + }, + input_descriptors: [ + { + id: 'bf8669f4-0cf3-4d16-b72b-b47eb702a7cd', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: [], + areRequiredCredentialsPresent: 'error', + }, + ], + }, + { + id: 1, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'pick', + count: 1, + }, + input_descriptors: [ + { + id: '99fce09b-a0d3-415b-b8a7-3eab8829babc', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], + }, + ], + rule: { + type: 'all', + count: 2, + }, + }, + { + id: 3, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'all', + count: 1, + }, + input_descriptors: [ + { + id: '99fce09b-a0d3-415b-b8a7-3eab8829babc', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], + }, + { + id: 4, + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: 'error', + rule: { + type: 'all', + count: 1, + }, + input_descriptors: [ + { + id: 'bf8669f4-0cf3-4d16-b72b-b47eb702a7cd', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: [], + areRequiredCredentialsPresent: 'error', + }, + ], + }, + ], + areRequiredCredentialsPresent: 'error', + verifiableCredential: [ + 'eyJ4NWMiOlsiTUlJQ2REQ0NBaHVnQXdJQkFnSUJBakFLQmdncWhrak9QUVFEQWpDQmlERUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneEVUQVBCZ05WQkFzTUNGUWdRMU1nU1VSRk1UWXdOQVlEVlFRRERDMVRVRkpKVGtRZ1JuVnVhMlVnUlZWRVNTQlhZV3hzWlhRZ1VISnZkRzkwZVhCbElFbHpjM1ZwYm1jZ1EwRXdIaGNOTWpRd05UTXhNRGd4TXpFM1doY05NalV3TnpBMU1EZ3hNekUzV2pCc01Rc3dDUVlEVlFRR0V3SkVSVEVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hNakF3QmdOVkJBTU1LVk5RVWtsT1JDQkdkVzVyWlNCRlZVUkpJRmRoYkd4bGRDQlFjbTkwYjNSNWNHVWdTWE56ZFdWeU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU9GQnE0WU1LZzR3NWZUaWZzeXR3QnVKZi83RTdWaFJQWGlObTUyUzNxMUVUSWdCZFh5REsza1Z4R3hnZUhQaXZMUDN1dU12UzZpREVjN3FNeG12ZHVLT0JrRENCalRBZEJnTlZIUTRFRmdRVWlQaENrTEVyRFhQTFcyL0owV1ZlZ2h5dyttSXdEQVlEVlIwVEFRSC9CQUl3QURBT0JnTlZIUThCQWY4RUJBTUNCNEF3TFFZRFZSMFJCQ1l3SklJaVpHVnRieTV3YVdRdGFYTnpkV1Z5TG1KMWJtUmxjMlJ5ZFdOclpYSmxhUzVrWlRBZkJnTlZIU01FR0RBV2dCVFVWaGpBaVRqb0RsaUVHTWwyWXIrcnU4V1F2akFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUFiZjVUemtjUXpoZldvSW95aTFWTjdkOEk5QnNGS20xTVdsdVJwaDJieUdRSWdLWWtkck5mMnhYUGpWU2JqVy9VLzVTNXZBRUM1WHhjT2FudXNPQnJvQmJVPSIsIk1JSUNlVENDQWlDZ0F3SUJBZ0lVQjVFOVFWWnRtVVljRHRDaktCL0gzVlF2NzJnd0NnWUlLb1pJemowRUF3SXdnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUI0WERUSTBNRFV6TVRBMk5EZ3dPVm9YRFRNME1EVXlPVEEyTkRnd09Wb3dnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFWUd6ZHdGRG5jNytLbjVpYkF2Q09NOGtlNzdWUXhxZk1jd1pMOElhSUErV0NST2NDZm1ZL2dpSDkycU1ydTVwL2t5T2l2RTBSQy9JYmRNT052RG9VeWFObU1HUXdIUVlEVlIwT0JCWUVGTlJXR01DSk9PZ09XSVFZeVhaaXY2dTd4WkMrTUI4R0ExVWRJd1FZTUJhQUZOUldHTUNKT09nT1dJUVl5WFppdjZ1N3haQytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnR0dNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJR0VtN3drWktIdC9hdGI0TWRGblhXNnlybndNVVQydTEzNmdkdGwxMFk2aEFpQnVURnF2Vll0aDFyYnh6Q1AweFdaSG1RSzlrVnl4bjhHUGZYMjdFSXp6c3c9PSJdLCJraWQiOiJNSUdVTUlHT3BJR0xNSUdJTVFzd0NRWURWUVFHRXdKRVJURVBNQTBHQTFVRUJ3d0dRbVZ5YkdsdU1SMHdHd1lEVlFRS0RCUkNkVzVrWlhOa2NuVmphMlZ5WldrZ1IyMWlTREVSTUE4R0ExVUVDd3dJVkNCRFV5QkpSRVV4TmpBMEJnTlZCQU1NTFZOUVVrbE9SQ0JHZFc1clpTQkZWVVJKSUZkaGJHeGxkQ0JRY205MGIzUjVjR1VnU1hOemRXbHVaeUJEUVFJQkFnPT0iLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiVS01ZlVXLU5EM1laajZTcUdyQXV4NXJWYWZOalhqZ2hvMmRUUmpQX3hOTSJdfSwiX3NkIjpbIjlFaUpQNEw2NDI0bEtTVGs5NHpIOWhaWVc5UjNuS1R3V0V5TVBJN2dvWHciLCJHVlhRWEtFMmpWR1d0VEF6T1d5ck85TTZySW1qYkZJWGFnRkMyWElMbGhJIiwiUUV2bHpNd0ozZS1tOEtpWEk5bGx2bnVQblh5UHRXN2VCSF9GcXFVTnk3WSIsImljWkpTRkFqLVg3T29Sam5vRFRReXFwU1dNQUVuaTcydWZDZmFFWC1uQkUiLCJsUHJqb3BqbEN5bFdHWVo0cmh4S1RUTUsxS3p1Sm5ISUtybzNwUUhlUXF3IiwicjJORHZtRFY3QmU3TlptVFR0VE9fekdZX3RTdWdYVXoxeDJBXzZuOFhvdyIsInJPbjFJUkpUQWtEV1pSTGc3MUYzaDVsbFpPc1ZPMl9aemlOUy1majNEUFUiXSwiYWRkcmVzcyI6eyJfc2QiOlsiQnI1aVZtZnZlaTloQ01mMktVOGRFVjFER2hrdUtsQ1pUeGFEQ0FMb3NJbyIsIkx6czJpR09SNHF0clhhYmdwMzFfcjFFUFNmazlaUDJQRElJUTRQaHlPT00iLCJadUV5cG41Y0s0WVpWdHdkeGFoWXJqMjZ1MFI2UmxpOVVJWlNjUGhoWTB3Iiwidi1rMzl2VGI5NFI5a25VWTZtbzlXUVdEQkNJS3lya0J4bExTQVl3T2MyNCJdfSwiaXNzdWluZ19jb3VudHJ5IjoiREUiLCJ2Y3QiOiJodHRwczovL2V4YW1wbGUuYm1pLmJ1bmQuZGUvY3JlZGVudGlhbC9waWQvMS4wIiwiaXNzdWluZ19hdXRob3JpdHkiOiJERSIsIl9zZF9hbGciOiJzaGEtMjU2IiwiaXNzIjoiaHR0cHM6Ly9kZW1vLnBpZC1pc3N1ZXIuYnVuZGVzZHJ1Y2tlcmVpLmRlL2MxIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IkhzS194Tl95SVU4eWlqdW9BWlhsbndFRU00ZlhZenVNRmd5TTE5SmRYMUkiLCJ5IjoiQUR2NnplVDl3YmgxU0ZxMG14TkcxMUZueC05eFdSRFcwR18xN1dSRXpRSSJ9fSwiZXhwIjoxNzMzNTcxMzI3LCJpYXQiOjE3MzIzNjE3MjcsImFnZV9lcXVhbF9vcl9vdmVyIjp7Il9zZCI6WyJLRDF0U0hnYWotZi1qbkZURkRDMW1sZ0RwNzhMZE1KcHlqWnRRU0k4a1ZnIiwiTDRjTTMtZU1mRHg0Znc2UEw3OVRTVFBnM042VXdzOGNPc3JOYmNqaEEtYyIsImRYUFBQX2lmNFM3XzBzcXZXNTBwZEdlMWszbS1wMnM3M1JicDlncThGaDAiLCJtYnllcU05YUkzRkVvWmFoODA5eTN0dlRCV1NvZTBMSlRUYTlONGNjdmlZIiwicm1zd0dEZnhvS0ZFYlFsNzZ4S1ZVT0hrX0MyQlVpVnQ5RDlvMTFrMmZNSSIsInZsY2Y4WTNhQnNTeEZBeVZfYk9NTndvX3FTT1pHc3ViSVZiY0FVSWVBSGMiXX19.gruqjNOuJBgHXEnG9e60wOoqiyEaL1K9pdL215a0ffZCjtIZ_kICDrO5vBiTrEmvjjd6w_N_thEYLhzob77Epg~WyJWRXlWQWF0LXoyNU8tbkQ0MVBaOGdnIiwiZmFtaWx5X25hbWUiLCJNVVNURVJNQU5OIl0~WyJLcnRPei1lRk9hMU9JYmpmUHUxcHRBIiwiZ2l2ZW5fbmFtZSIsIkVSSUtBIl0~WyJQQUVjSHp0NWk5bFFzNUZlRmFGUS1RIiwiYmlydGhkYXRlIiwiMTk2NC0wOC0xMiJd~', + ], + warnings: [], + vcIndexes: [0], + }); + }); }); diff --git a/test/evaluation/EvaluationClientWrapperData.ts b/test/evaluation/EvaluationClientWrapperData.ts index 62418330..5dcdf98f 100644 --- a/test/evaluation/EvaluationClientWrapperData.ts +++ b/test/evaluation/EvaluationClientWrapperData.ts @@ -1,4 +1,4 @@ -import { PresentationSubmission, Rules } from '@sphereon/pex-models'; +import { PresentationSubmission } from '@sphereon/pex-models'; import { IVerifiableCredential } from '@sphereon/ssi-types'; import { HandlerCheckResult, SelectResults, Status } from '../../lib'; @@ -196,7 +196,7 @@ export class EvaluationClientWrapperData { matches: [ { name: 'test', - rule: Rules.All, + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[0]'], id: 'Educational transcripts', type: SubmissionRequirementMatchType.InputDescriptor, diff --git a/test/evaluation/check-scenario-1.spec.ts b/test/evaluation/check-scenario-1.spec.ts index 01928b3d..8b2775b6 100644 --- a/test/evaluation/check-scenario-1.spec.ts +++ b/test/evaluation/check-scenario-1.spec.ts @@ -205,16 +205,14 @@ describe('1st scenario', () => { expect(selectFromResult.matches?.length).toEqual(2); expect(selectFromResult.matches).toEqual([ { - rule: 'all', + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[0]'], - name: 'e73646de-43e2-4d72-ba4f-090d01c11eac', type: SubmissionRequirementMatchType.InputDescriptor, id: 'e73646de-43e2-4d72-ba4f-090d01c11eac', }, { - rule: 'all', + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[0]'], - name: '867bfe7a-5b91-46b2-9ba4-70028b8d9cc8', type: SubmissionRequirementMatchType.InputDescriptor, id: '867bfe7a-5b91-46b2-9ba4-70028b8d9cc8', }, diff --git a/test/evaluation/core/submissionRequirementMatch.spec.ts b/test/evaluation/core/submissionRequirementMatch.spec.ts index 3df213b1..00f67836 100644 --- a/test/evaluation/core/submissionRequirementMatch.spec.ts +++ b/test/evaluation/core/submissionRequirementMatch.spec.ts @@ -7,14 +7,25 @@ describe('submissionRequirementMatch', () => { it('should return ok constructor works correctly', function () { const submissionRequirementMatch: SubmissionRequirementMatch = { name: 'test srm', - rule: Rules.All, - vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: 'info', from: 'A', id: 0, + rule: { + count: 2, + type: 'all', + }, + input_descriptors: [ + { + areRequiredCredentialsPresent: 'info', + id: '0c940f06-3d61-47dd-ae3e-db40509d8118', + vc_path: ['$.verifiableCredential[1]'], + type: SubmissionRequirementMatchType.InputDescriptor, + }, + ], type: SubmissionRequirementMatchType.SubmissionRequirement, }; expect(submissionRequirementMatch.from).toContain('A'); - expect(submissionRequirementMatch.rule).toBe(Rules.All); - expect(submissionRequirementMatch.vc_path[0]).toBe('$.verifiableCredential[1]'); + expect(submissionRequirementMatch.rule.type).toBe(Rules.All); + expect(submissionRequirementMatch.input_descriptors[0].vc_path[0]).toBe('$.verifiableCredential[1]'); }); }); diff --git a/test/evaluation/evaluationClientWrapper.spec.ts b/test/evaluation/evaluationClientWrapper.spec.ts index 9e1842e1..06fb386d 100644 --- a/test/evaluation/evaluationClientWrapper.spec.ts +++ b/test/evaluation/evaluationClientWrapper.spec.ts @@ -365,16 +365,6 @@ describe('evaluate', () => { ); }); - it('should map successfully the links from selectable credentials to verifiable credentials.', () => { - const selectResults = evaluationClientWrapperData.getSelectResults(); - new EvaluationClientWrapper().fillSelectableCredentialsToVerifiableCredentialsMapping( - selectResults, - SSITypesBuilder.mapExternalVerifiableCredentialsToWrappedVcs(evaluationClientWrapperData.getVerifiableCredential()), - ); - const verifiableCredential = selectResults.verifiableCredential![0] as IVerifiableCredential; - expect(verifiableCredential.id).toEqual((evaluationClientWrapperData.getVerifiableCredential()[1]).id); - }); - it('should pass with correct submissionFrom result name and roles with 2 groups', function () { const clientWrapper: EvaluationClientWrapper = new EvaluationClientWrapper(); const pdSchema: PresentationDefinitionV2 = { @@ -502,7 +492,7 @@ describe('evaluate', () => { holderDIDs: undefined, limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, }); - expect(resultSelectFrom.areRequiredCredentialsPresent).toEqual(Status.WARN); + expect(resultSelectFrom.areRequiredCredentialsPresent).toEqual(Status.INFO); const ps: PresentationSubmission = clientWrapper.submissionFrom(internalPD, wvcs); expect(ps.descriptor_map.map((d) => d.path)).toEqual(['$.verifiableCredential[0]', '$.verifiableCredential[0]', '$.verifiableCredential[1]']); }); @@ -602,7 +592,7 @@ describe('evaluate', () => { holderDIDs: undefined, limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, }); - expect(resultSelectFrom.areRequiredCredentialsPresent).toEqual(Status.WARN); + expect(resultSelectFrom.areRequiredCredentialsPresent).toEqual(Status.INFO); expect(resultSelectFrom.verifiableCredential?.length).toEqual(1); const ps: PresentationSubmission = clientWrapper.submissionFrom( internalPD, diff --git a/test/evaluation/selectFrom.spec.ts b/test/evaluation/selectFrom.spec.ts index e297e59e..b7c364ce 100644 --- a/test/evaluation/selectFrom.spec.ts +++ b/test/evaluation/selectFrom.spec.ts @@ -2,7 +2,6 @@ import { createHash } from 'crypto'; import fs from 'fs'; import { SDJwt } from '@sd-jwt/core'; -import { Rules } from '@sphereon/pex-models'; import { IVerifiableCredential, WrappedVerifiableCredential } from '@sphereon/ssi-types'; import { PEX, Status } from '../../lib'; @@ -53,87 +52,42 @@ describe('selectFrom tests', () => { matches: [ { from: 'A', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]', '$.verifiableCredential[2]'], name: 'Submission of educational transcripts', - rule: 'all', id: 0, - type: SubmissionRequirementMatchType.SubmissionRequirement, - }, - ], - verifiableCredential: [ - { - iss: 'did:example:123', - FIXME: 'THIS DOESNT MAKE SENSE. The is a decoded JWT as an object in the array. It should just be a JWT VC as string', - vc: { - '@context': 'https://eu.com/claims/DriversLicense', - credentialSubject: { - accounts: [ - { - id: '1234567890', - route: 'DE-9876543210', - }, - { - id: '2457913570', - route: 'DE-0753197542', - }, - ], - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:example:123', - type: ['VerifiableCredential', 'EUDriversLicense'], - }, - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - }, - { - '@context': 'https://business-standards.org/schemas/employment-history.json', - credentialSubject: { - active: true, - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'all', + count: 3, }, - id: 'https://business-standards.org/schemas/employment-history.json', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'GenericEmploymentCredential'], - }, - { - '@context': 'https://www.w3.org/2018/credentials/v1', - credentialSubject: { - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - license: { - dob: '07/13/80', - number: '34DGE352', + input_descriptors: [ + { + areRequiredCredentialsPresent: Status.INFO, + id: 'Educational transcripts', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], }, - }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'RsaSignature2018', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'EUDriversLicense'], + { + areRequiredCredentialsPresent: Status.INFO, + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + }, + { + areRequiredCredentialsPresent: Status.INFO, + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + }, + ], + type: SubmissionRequirementMatchType.SubmissionRequirement, }, ], + verifiableCredential: [vpSimple.verifiableCredential[0], vpSimple.verifiableCredential[1], vpSimple.verifiableCredential[2]], warnings: [], - vcIndexes: [0, 2], + vcIndexes: [0, 1, 2], }); }); @@ -177,58 +131,36 @@ describe('selectFrom tests', () => { errors: [], matches: [ { - from: 'B', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]'], - min: 2, - name: 'Eligibility to Work Proof', - rule: 'pick', id: 0, + name: 'Eligibility to Work Proof', type: SubmissionRequirementMatchType.SubmissionRequirement, - }, - ], - verifiableCredential: [ - { - '@context': 'https://business-standards.org/schemas/employment-history.json', - credentialSubject: { - active: true, - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - }, - id: 'https://business-standards.org/schemas/employment-history.json', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'pick', + min: 2, }, - type: ['VerifiableCredential', 'GenericEmploymentCredential'], - }, - { - '@context': 'https://www.w3.org/2018/credentials/v1', - credentialSubject: { - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - license: { - dob: '07/13/80', - number: '34DGE352', + input_descriptors: [ + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, }, - }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'RsaSignature2018', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'EUDriversLicense'], + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, ], + verifiableCredential: [vpSimple.verifiableCredential[1], vpSimple.verifiableCredential[2]], warnings: [], - vcIndexes: [0, 2], + vcIndexes: [1, 2], }); }); @@ -272,58 +204,36 @@ describe('selectFrom tests', () => { errors: [], matches: [ { - from: 'B', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]'], - max: 2, - rule: 'pick', id: 0, name: 'Eligibility to Work Proof', type: SubmissionRequirementMatchType.SubmissionRequirement, - }, - ], - verifiableCredential: [ - { - '@context': 'https://business-standards.org/schemas/employment-history.json', - credentialSubject: { - active: true, - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - }, - id: 'https://business-standards.org/schemas/employment-history.json', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'pick', + max: 2, }, - type: ['VerifiableCredential', 'GenericEmploymentCredential'], - }, - { - '@context': 'https://www.w3.org/2018/credentials/v1', - credentialSubject: { - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - license: { - dob: '07/13/80', - number: '34DGE352', + input_descriptors: [ + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, }, - }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'RsaSignature2018', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'EUDriversLicense'], + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, ], + verifiableCredential: [vpSimple.verifiableCredential[1], vpSimple.verifiableCredential[2]], warnings: [], - vcIndexes: [0, 2], + vcIndexes: [1, 2], }); }); @@ -348,108 +258,80 @@ describe('selectFrom tests', () => { errors: [], matches: [ { + id: 0, + areRequiredCredentialsPresent: Status.INFO, + name: 'Confirm banking relationship or employment and residence proofs', + type: SubmissionRequirementMatchType.SubmissionRequirement, from_nested: [ { - from: 'A', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]', '$.verifiableCredential[2]'], - rule: 'all', id: 0, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'all', + count: 3, + }, + input_descriptors: [ + { + id: 'Educational transcripts', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, { - count: 2, - from: 'B', - vc_path: ['$.verifiableCredential[1]', '$.verifiableCredential[2]'], - rule: 'pick', id: 1, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, - }, - ], - vc_path: [], - rule: 'all', - id: 0, - name: 'Confirm banking relationship or employment and residence proofs', - type: SubmissionRequirementMatchType.SubmissionRequirement, - }, - ], - verifiableCredential: [ - { - FIXME: 'THIS DOESNT MAKE SENSE. The is a decoded JWT as an object in the array. It should just be a JWT VC as string', - iss: 'did:example:123', - vc: { - '@context': 'https://eu.com/claims/DriversLicense', - credentialSubject: { - accounts: [ + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'pick', + count: 2, + }, + input_descriptors: [ { - id: '1234567890', - route: 'DE-9876543210', + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, }, { - id: '2457913570', - route: 'DE-0753197542', + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, }, ], - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:example:123', - type: ['VerifiableCredential', 'EUDriversLicense'], - }, - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - }, - { - '@context': 'https://business-standards.org/schemas/employment-history.json', - credentialSubject: { - active: true, - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - }, - id: 'https://business-standards.org/schemas/employment-history.json', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'GenericEmploymentCredential'], - }, - { - '@context': 'https://www.w3.org/2018/credentials/v1', - credentialSubject: { - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - license: { - dob: '07/13/80', - number: '34DGE352', }, + ], + rule: { + type: 'all', + count: 2, }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'RsaSignature2018', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'EUDriversLicense'], }, ], + verifiableCredential: [vpSimple.verifiableCredential[0], vpSimple.verifiableCredential[1], vpSimple.verifiableCredential[2]], warnings: [], - vcIndexes: [0, 2], + vcIndexes: [0, 1, 2], }); }); @@ -474,109 +356,80 @@ describe('selectFrom tests', () => { errors: [], matches: [ { + id: 0, + areRequiredCredentialsPresent: Status.INFO, + name: 'Confirm banking relationship or employment and residence proofs', + type: SubmissionRequirementMatchType.SubmissionRequirement, from_nested: [ { - from: 'A', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]', '$.verifiableCredential[2]'], - rule: 'all', id: 0, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'all', + count: 3, + }, + input_descriptors: [ + { + id: 'Educational transcripts', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, { - count: 2, - from: 'B', - vc_path: ['$.verifiableCredential[1]', '$.verifiableCredential[2]'], - rule: 'pick', id: 1, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, - }, - ], - vc_path: [], - min: 1, - rule: 'pick', - id: 0, - name: 'Confirm banking relationship or employment and residence proofs', - type: SubmissionRequirementMatchType.SubmissionRequirement, - }, - ], - verifiableCredential: [ - { - iss: 'did:example:123', - FIXME: 'THIS DOESNT MAKE SENSE. The is a decoded JWT as an object in the array. It should just be a JWT VC as string', - vc: { - '@context': 'https://eu.com/claims/DriversLicense', - credentialSubject: { - accounts: [ + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'pick', + count: 2, + }, + input_descriptors: [ { - id: '1234567890', - route: 'DE-9876543210', + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, }, { - id: '2457913570', - route: 'DE-0753197542', + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, }, ], - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:example:123', - type: ['VerifiableCredential', 'EUDriversLicense'], - }, - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - }, - { - '@context': 'https://business-standards.org/schemas/employment-history.json', - credentialSubject: { - active: true, - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - }, - id: 'https://business-standards.org/schemas/employment-history.json', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'GenericEmploymentCredential'], - }, - { - '@context': 'https://www.w3.org/2018/credentials/v1', - credentialSubject: { - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - license: { - dob: '07/13/80', - number: '34DGE352', }, + ], + rule: { + type: 'pick', + min: 1, }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'RsaSignature2018', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'EUDriversLicense'], }, ], + verifiableCredential: [vpSimple.verifiableCredential[0], vpSimple.verifiableCredential[1], vpSimple.verifiableCredential[2]], warnings: [], - vcIndexes: [0, 2], + vcIndexes: [0, 1, 2], }); }); @@ -597,113 +450,84 @@ describe('selectFrom tests', () => { limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, }), ).toEqual({ - areRequiredCredentialsPresent: Status.INFO, errors: [], matches: [ { + id: 0, + areRequiredCredentialsPresent: Status.INFO, + name: 'Confirm banking relationship or employment and residence proofs', + type: SubmissionRequirementMatchType.SubmissionRequirement, from_nested: [ { - from: 'A', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]', '$.verifiableCredential[2]'], - rule: 'all', id: 0, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'all', + count: 3, + }, + input_descriptors: [ + { + id: 'Educational transcripts', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, { - count: 2, - from: 'B', - vc_path: ['$.verifiableCredential[1]', '$.verifiableCredential[2]'], - rule: 'pick', id: 1, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, - }, - ], - vc_path: [], - max: 2, - rule: 'pick', - id: 0, - name: 'Confirm banking relationship or employment and residence proofs', - type: SubmissionRequirementMatchType.SubmissionRequirement, - }, - ], - verifiableCredential: [ - { - iss: 'did:example:123', - FIXME: 'THIS DOESNT MAKE SENSE. The is a decoded JWT as an object in the array. It should just be a JWT VC as string', - vc: { - '@context': 'https://eu.com/claims/DriversLicense', - credentialSubject: { - accounts: [ + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'pick', + count: 2, + }, + input_descriptors: [ { - id: '1234567890', - route: 'DE-9876543210', + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, }, { - id: '2457913570', - route: 'DE-0753197542', + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, }, ], - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:example:123', - type: ['VerifiableCredential', 'EUDriversLicense'], - }, - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - }, - { - '@context': 'https://business-standards.org/schemas/employment-history.json', - credentialSubject: { - active: true, - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - }, - id: 'https://business-standards.org/schemas/employment-history.json', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'EcdsaSecp256k1VerificationKey2019', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'GenericEmploymentCredential'], - }, - { - '@context': 'https://www.w3.org/2018/credentials/v1', - credentialSubject: { - id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', - license: { - dob: '07/13/80', - number: '34DGE352', }, + ], + rule: { + type: 'pick', + max: 2, }, - id: 'https://eu.com/claims/DriversLicense', - issuanceDate: '2010-01-01T19:73:24Z', - issuer: 'did:foo:123', - proof: { - created: '2017-06-18T21:19:10Z', - jws: '...', - proofPurpose: 'assertionMethod', - type: 'RsaSignature2018', - verificationMethod: 'https://example.edu/issuers/keys/1', - }, - type: ['VerifiableCredential', 'EUDriversLicense'], }, ], + areRequiredCredentialsPresent: Status.INFO, + verifiableCredential: [vpSimple.verifiableCredential[0], vpSimple.verifiableCredential[1], vpSimple.verifiableCredential[2]], warnings: [], - vcIndexes: [0, 2], + vcIndexes: [0, 1, 2], }); }); @@ -807,13 +631,31 @@ describe('selectFrom tests', () => { ]); expect(result.matches).toEqual([ { - from: 'B', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]'], - min: 3, - rule: 'pick', id: 0, - type: SubmissionRequirementMatchType.SubmissionRequirement, name: 'Eligibility to Work Proof', + type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'B', + areRequiredCredentialsPresent: Status.ERROR, + rule: { + type: 'pick', + min: 3, + }, + input_descriptors: [ + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, ]); }); @@ -836,16 +678,34 @@ describe('selectFrom tests', () => { expect(result.matches).toEqual([ { from: 'B', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]'], - max: 1, - rule: 'pick', + rule: { + type: 'pick', + max: 1, + }, + areRequiredCredentialsPresent: Status.WARN, + input_descriptors: [ + { + areRequiredCredentialsPresent: Status.INFO, + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + }, + { + areRequiredCredentialsPresent: Status.INFO, + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + }, + ], type: SubmissionRequirementMatchType.SubmissionRequirement, name: 'Eligibility to Work Proof', id: 0, }, ]); expect(result.errors?.length).toEqual(16); - expect(result.verifiableCredential?.length).toEqual(3); + expect(result.verifiableCredential?.length).toEqual(2); expect(result.areRequiredCredentialsPresent).toEqual(Status.WARN); }); @@ -865,7 +725,7 @@ describe('selectFrom tests', () => { limitDisclosureSignatureSuites: LIMIT_DISCLOSURE_SIGNATURE_SUITES, }); expect(result.matches?.length).toEqual(1); - expect(result.verifiableCredential?.length).toEqual(3); + expect(result.verifiableCredential?.length).toEqual(2); expect(result.errors?.length).toEqual(16); expect(result.areRequiredCredentialsPresent).toEqual(Status.WARN); }); @@ -910,33 +770,75 @@ describe('selectFrom tests', () => { expect(result.areRequiredCredentialsPresent).toEqual(Status.INFO); expect(result.matches?.length).toEqual(1); expect(result.matches![0]).toEqual({ + id: 0, + areRequiredCredentialsPresent: Status.INFO, + name: 'Confirm banking relationship or employment and residence proofs', + type: SubmissionRequirementMatchType.SubmissionRequirement, from_nested: [ { - from: 'A', - rule: Rules.All, - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]', '$.verifiableCredential[2]'], id: 0, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'all', + count: 3, + }, + input_descriptors: [ + { + id: 'Educational transcripts', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, { - count: 2, - from: 'B', - rule: Rules.Pick, - vc_path: ['$.verifiableCredential[1]', '$.verifiableCredential[2]'], id: 1, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'pick', + count: 2, + }, + input_descriptors: [ + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, ], - min: 1, - name: 'Confirm banking relationship or employment and residence proofs', - rule: Rules.Pick, - vc_path: [], - id: 0, - type: SubmissionRequirementMatchType.SubmissionRequirement, + rule: { + type: 'pick', + min: 1, + }, }); }); @@ -958,34 +860,75 @@ describe('selectFrom tests', () => { expect(result.areRequiredCredentialsPresent).toEqual(Status.WARN); expect(result.matches).toEqual([ { + id: 0, + areRequiredCredentialsPresent: Status.WARN, + name: 'Confirm banking relationship or employment and residence proofs', + type: SubmissionRequirementMatchType.SubmissionRequirement, from_nested: [ { - from: 'A', - vc_path: ['$.verifiableCredential[0]', '$.verifiableCredential[1]', '$.verifiableCredential[2]'], - rule: 'all', id: 0, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'A', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'all', + count: 3, + }, + input_descriptors: [ + { + id: 'Educational transcripts', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, { - count: 2, - from: 'B', - vc_path: ['$.verifiableCredential[1]', '$.verifiableCredential[2]'], - rule: 'pick', id: 1, - // submission requirement from_nested has no name - name: undefined, type: SubmissionRequirementMatchType.SubmissionRequirement, + from: 'B', + areRequiredCredentialsPresent: Status.INFO, + rule: { + type: 'pick', + count: 2, + }, + input_descriptors: [ + { + id: 'Educational transcripts 1', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: Status.INFO, + }, + { + id: 'Educational transcripts 2', + name: 'Submission of educational transcripts', + type: SubmissionRequirementMatchType.InputDescriptor, + vc_path: ['$.verifiableCredential[2]'], + areRequiredCredentialsPresent: Status.INFO, + }, + ], }, ], - vc_path: [], - // submission requirement name - name: 'Confirm banking relationship or employment and residence proofs', - rule: 'pick', - max: 1, - type: SubmissionRequirementMatchType.SubmissionRequirement, - id: 0, + rule: { + type: 'pick', + max: 1, + }, }, ]); expect(result.errors?.length).toEqual(16); @@ -1009,7 +952,7 @@ describe('selectFrom tests', () => { name: "EU Driver's License", id: 'citizenship_input_1', type: SubmissionRequirementMatchType.InputDescriptor, - rule: 'all', + areRequiredCredentialsPresent: Status.INFO, vc_path: ['$.verifiableCredential[0]'], }); }); @@ -1051,8 +994,6 @@ describe('selectFrom tests', () => { const cred = await SDJwt.fromEncode(presentationResult.presentations[1].compactSdJwtVc, 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'); @@ -1092,7 +1033,6 @@ describe('selectFrom tests', () => { expect(presentationResult).toBeDefined(); 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'); diff --git a/test/thirdParty/Animo.spec.ts b/test/thirdParty/Animo.spec.ts index d5239225..ef4b266d 100644 --- a/test/thirdParty/Animo.spec.ts +++ b/test/thirdParty/Animo.spec.ts @@ -2,7 +2,6 @@ import { Rules } from '@sphereon/pex-models'; import { W3CVerifiableCredential } from '@sphereon/ssi-types'; import { IPresentationDefinition, PEX, Status } from '../../lib'; -import { SubmissionRequirementMatchType } from '../../lib/evaluation/core'; describe('evaluate animo tests', () => { it('should pass with 2 VCs and 2 IDs', () => { @@ -136,22 +135,40 @@ describe('evaluate animo tests', () => { expect(result.areRequiredCredentialsPresent).toEqual(Status.INFO); expect(result.matches).toEqual([ { - count: 1, - from: 'A', id: 0, - name: undefined, - rule: 'pick', - type: SubmissionRequirementMatchType.SubmissionRequirement, - vc_path: ['$.verifiableCredential[0]'], + type: 'SubmissionRequirement', + from: 'A', + areRequiredCredentialsPresent: 'info', + rule: { + type: 'pick', + count: 1, + }, + input_descriptors: [ + { + id: 'c2834d0e-3c95-4721-b21a-40e3d7ea2549', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: 'info', + }, + ], }, { - count: 1, - from: 'B', id: 1, - name: undefined, - rule: 'pick', - type: SubmissionRequirementMatchType.SubmissionRequirement, - vc_path: ['$.verifiableCredential[1]'], + type: 'SubmissionRequirement', + from: 'B', + areRequiredCredentialsPresent: 'info', + rule: { + type: 'pick', + count: 1, + }, + input_descriptors: [ + { + id: 'c2834d0e-3c95-4721-b21a-40e3d7ea25434', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: 'info', + }, + ], }, ]); }); diff --git a/test/thirdParty/Gataca.spec.ts b/test/thirdParty/Gataca.spec.ts index aa431b9f..f2c99e70 100644 --- a/test/thirdParty/Gataca.spec.ts +++ b/test/thirdParty/Gataca.spec.ts @@ -2,7 +2,6 @@ import { PresentationDefinitionV1 } from '@sphereon/pex-models'; import { IPresentation, IProofType, IVerifiableCredential } from '@sphereon/ssi-types'; import { PEX, PEXv1, Status } from '../../lib'; -import { SubmissionRequirementMatchType } from '../../lib/evaluation/core'; import { GatacaPresentationDefinition } from '../test_data/gataca/gatacaPresentationDefinition'; import { GatacaSelectedCredentials } from '../test_data/gataca/gatacaSelectedCredentials'; @@ -26,20 +25,47 @@ describe('evaluate gataca tests', () => { expect(result.areRequiredCredentialsPresent).toEqual(Status.INFO); expect(result.matches).toEqual([ { - rule: 'all', - from: 'mandatory', - vc_path: ['$.verifiableCredential[0]'], id: 0, name: 'Mandatory data', - type: SubmissionRequirementMatchType.SubmissionRequirement, + type: 'SubmissionRequirement', + from: 'mandatory', + areRequiredCredentialsPresent: 'info', + rule: { + type: 'all', + count: 1, + }, + input_descriptors: [ + { + id: 'emailCredential', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: 'info', + }, + ], }, { - rule: 'pick', - from: 'optional', - vc_path: ['$.verifiableCredential[1]'], id: 1, name: 'Optional data', - type: SubmissionRequirementMatchType.SubmissionRequirement, + type: 'SubmissionRequirement', + from: 'optional', + areRequiredCredentialsPresent: 'info', + rule: { + type: 'pick', + }, + input_descriptors: [ + { + id: 'phoneCredential', + type: 'InputDescriptor', + vc_path: [], + areRequiredCredentialsPresent: 'error', + }, + { + id: 'transcriptOfRecordsCredential', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: 'info', + }, + ], }, ]); expect(result.verifiableCredential?.length).toEqual(2); @@ -104,20 +130,51 @@ describe('evaluate gataca tests', () => { expect(result.areRequiredCredentialsPresent).toEqual(Status.INFO); expect(result.matches).toEqual([ { - rule: 'all', - from: 'mandatory', - vc_path: ['$.verifiableCredential[0]'], id: 0, name: 'Mandatory data', - type: SubmissionRequirementMatchType.SubmissionRequirement, + type: 'SubmissionRequirement', + from: 'mandatory', + areRequiredCredentialsPresent: 'info', + rule: { + type: 'all', + count: 1, + }, + input_descriptors: [ + { + id: 'emailCredential', + name: 'emailCredential', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[0]'], + areRequiredCredentialsPresent: 'info', + }, + ], }, { - rule: 'pick', - from: 'optional', - vc_path: ['$.verifiableCredential[1]'], id: 1, name: 'Optional data', - type: SubmissionRequirementMatchType.SubmissionRequirement, + type: 'SubmissionRequirement', + from: 'optional', + areRequiredCredentialsPresent: 'info', + rule: { + type: 'pick', + min: 0, + }, + input_descriptors: [ + { + id: 'phoneCredential', + type: 'InputDescriptor', + name: 'phoneCredential', + vc_path: [], + areRequiredCredentialsPresent: 'error', + }, + { + id: 'transcriptOfRecordsCredential', + name: 'transcriptOfRecordsCredential', + type: 'InputDescriptor', + vc_path: ['$.verifiableCredential[1]'], + areRequiredCredentialsPresent: 'info', + }, + ], }, ]); expect(result.verifiableCredential?.length).toEqual(2); diff --git a/test/thirdParty/JGiter.spec.ts b/test/thirdParty/JGiter.spec.ts index c21cf142..4d849d1f 100644 --- a/test/thirdParty/JGiter.spec.ts +++ b/test/thirdParty/JGiter.spec.ts @@ -1,9 +1,9 @@ -import { PresentationDefinitionV2, Rules } from '@sphereon/pex-models'; +import { PresentationDefinitionV2 } from '@sphereon/pex-models'; import { IPresentation, IProofType, IVerifiableCredential } from '@sphereon/ssi-types'; import { EvaluationResults, PEX, Status } from '../../lib'; import { PresentationEvaluationResults } from '../../lib/evaluation'; -import { SubmissionRequirementMatchType } from '../../lib/evaluation/core'; +import { SubmissionRequirementMatchFrom, SubmissionRequirementMatchType } from '../../lib/evaluation/core'; const LIMIT_DISCLOSURE_SIGNATURE_SUITES = [IProofType.BbsBlsSignatureProof2020]; @@ -496,13 +496,13 @@ describe('evaluate JGiter tests', () => { const vcs = getVerifiableCredentials(); const selectFrom = pex.selectFrom(pdSchema, vcs); - expect(selectFrom.errors?.length).toEqual(6); - expect(selectFrom.areRequiredCredentialsPresent).toEqual(Status.WARN); + expect(selectFrom.errors?.length).toEqual(0); + expect(selectFrom.areRequiredCredentialsPresent).toEqual(Status.INFO); expect(selectFrom.verifiableCredential?.length).toEqual(2); - expect(selectFrom.matches![0]?.from).toEqual('A'); - expect(selectFrom.matches![0]?.vc_path).toEqual(['$.verifiableCredential[0]']); - expect(selectFrom.matches![1]?.from).toEqual('B'); - expect(selectFrom.matches![1]?.vc_path).toEqual(['$.verifiableCredential[1]']); + expect((selectFrom.matches![0] as SubmissionRequirementMatchFrom)?.from).toEqual('A'); + expect((selectFrom.matches![0] as SubmissionRequirementMatchFrom)?.input_descriptors[0].vc_path).toEqual(['$.verifiableCredential[0]']); + expect((selectFrom.matches![1] as SubmissionRequirementMatchFrom)?.from).toEqual('B'); + expect((selectFrom.matches![1] as SubmissionRequirementMatchFrom)?.input_descriptors[0].vc_path).toEqual(['$.verifiableCredential[1]']); const presentationResult = pex.presentationFrom(pdSchema, selectFrom.verifiableCredential as IVerifiableCredential[]); const presentation = presentationResult.presentations[0] as IPresentation; expect(presentation.presentation_submission?.descriptor_map).toEqual([ @@ -531,8 +531,8 @@ describe('evaluate JGiter tests', () => { const pdSchema: PresentationDefinitionV2 = getPresentationDefinition_2(); const vcs = getVerifiableCredentials(); const selectResult = pex.selectFrom(pdSchema, vcs); - expect(selectResult.errors?.length).toEqual(6); - expect(selectResult.areRequiredCredentialsPresent).toEqual(Status.WARN); + expect(selectResult.errors?.length).toEqual(0); + expect(selectResult.areRequiredCredentialsPresent).toEqual(Status.INFO); }); it('Evaluate case with with single submission requirements (A pick min 2)', () => { @@ -564,7 +564,7 @@ describe('evaluate JGiter tests', () => { const pdSchema: PresentationDefinitionV2 = getPresentationDefinition_5(); const vcs = getVerifiableCredentials(); const selectResult = pex.selectFrom(pdSchema, vcs); - expect(selectResult.areRequiredCredentialsPresent).toBe(Status.ERROR); + expect(selectResult.areRequiredCredentialsPresent).toBe(Status.INFO); }); it('Evaluate case with with no submission requirements', () => { @@ -579,21 +579,21 @@ describe('evaluate JGiter tests', () => { expect(resultSelectFrom.matches).toEqual([ { name: 'Subject identity input', - rule: Rules.All, + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[0]'], id: 'identity_input', type: SubmissionRequirementMatchType.InputDescriptor, }, { name: 'Subject name input', - rule: Rules.All, + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[0]'], id: 'name_input', type: SubmissionRequirementMatchType.InputDescriptor, }, { name: 'Admin role input', - rule: Rules.All, + areRequiredCredentialsPresent: 'info', vc_path: ['$.verifiableCredential[1]'], id: 'role_input', type: SubmissionRequirementMatchType.InputDescriptor,