UNPKG

@credo-ts/core

Version:

<p align="center"> <br /> <img alt="Credo Logo" src="https://github.com/openwallet-foundation/credo-ts/blob/c7886cb8377ceb8ee4efe8d264211e561a75072d/images/credo-logo.png" height="250px" /> </p> <h1 align="center"><b>Credo - Core</b></h1

1 lines • 21.4 kB
{"version":3,"file":"credentialSelection.mjs","names":[],"sources":["../../../../src/modules/dif-presentation-exchange/utils/credentialSelection.ts"],"sourcesContent":["import {\n type IPresentationDefinition,\n type PEX,\n type SelectResults,\n Status,\n type SubmissionRequirementMatch,\n} from '@animo-id/pex'\nimport type {\n SubmissionRequirementMatchFrom,\n SubmissionRequirementMatchInputDescriptor,\n} from '@animo-id/pex/dist/main/lib/evaluation/core'\nimport { SubmissionRequirementMatchType } from '@animo-id/pex/dist/main/lib/evaluation/core/index.js'\nimport { JSONPath } from '@astronautlabs/jsonpath'\nimport { decodeSdJwtSync, getClaimsSync } from '@sd-jwt/decode'\nimport type { InputDescriptorV1, InputDescriptorV2, SubmissionRequirement } from '@sphereon/pex-models'\nimport { Rules } from '@sphereon/pex-models'\nimport type { AgentContext } from 'packages/core/src/agent'\nimport { Hasher } from '../../../crypto'\nimport { CredoError } from '../../../error'\nimport type { JsonObject } from '../../../types'\nimport { MdocRecord } from '../../mdoc'\nimport { MdocDeviceResponse } from '../../mdoc/MdocDeviceResponse'\nimport { SdJwtVcRecord } from '../../sd-jwt-vc'\nimport { ClaimFormat, W3cCredentialRecord } from '../../vc'\nimport { DifPresentationExchangeError } from '../DifPresentationExchangeError'\nimport type {\n DifPexCredentialsForRequest,\n DifPexCredentialsForRequestRequirement,\n DifPexCredentialsForRequestSubmissionEntry,\n SubmissionEntryCredential,\n} from '../models'\nimport { getSphereonOriginalVerifiableCredential } from './transform'\n\nexport async function getCredentialsForRequest(\n agentContext: AgentContext,\n // PEX instance with hasher defined\n pex: PEX,\n presentationDefinition: IPresentationDefinition,\n credentialRecords: Array<W3cCredentialRecord | SdJwtVcRecord | MdocRecord>\n): Promise<DifPexCredentialsForRequest> {\n const encodedCredentials = credentialRecords.map(getSphereonOriginalVerifiableCredential)\n const selectResultsRaw = pex.selectFrom(presentationDefinition, encodedCredentials)\n\n const selectResults: CredentialRecordSelectResults = {\n ...selectResultsRaw,\n matches: selectResultsRaw.matches ?? [],\n // Map the encoded credential to their respective credential record\n verifiableCredential:\n selectResultsRaw.verifiableCredential?.map((selectedEncoded, index): SubmissionEntryCredential => {\n const credentialRecordIndex = selectResultsRaw.vcIndexes?.[index]\n if (credentialRecordIndex === undefined || credentialRecordIndex === -1) {\n throw new DifPresentationExchangeError('Unable to find credential in credential records.')\n }\n const credentialRecord = credentialRecords[credentialRecordIndex]\n if (credentialRecord instanceof SdJwtVcRecord) {\n // selectedEncoded always string when SdJwtVcRecord\n // Get the decoded payload from the the selected credential, this already has SD applied\n const { jwt, disclosures } = decodeSdJwtSync(selectedEncoded as string, Hasher.hash)\n const prettyClaims = getClaimsSync(jwt.payload, disclosures, Hasher.hash)\n\n return {\n claimFormat: ClaimFormat.SdJwtDc,\n credentialRecord,\n disclosedPayload: prettyClaims as JsonObject,\n }\n }\n if (credentialRecord instanceof MdocRecord) {\n return {\n claimFormat: ClaimFormat.MsoMdoc,\n credentialRecord,\n disclosedPayload: {},\n }\n }\n if (credentialRecord instanceof W3cCredentialRecord) {\n return {\n claimFormat: credentialRecord.firstCredential.claimFormat,\n credentialRecord,\n }\n }\n throw new CredoError('Unrecognized credential record type')\n }) ?? [],\n }\n\n const presentationSubmission: DifPexCredentialsForRequest = {\n requirements: [],\n areRequirementsSatisfied: false,\n name: presentationDefinition.name,\n purpose: presentationDefinition.purpose,\n }\n\n // If there's no submission requirements, ALL input descriptors MUST be satisfied\n if (!presentationDefinition.submission_requirements || presentationDefinition.submission_requirements.length === 0) {\n presentationSubmission.requirements = getSubmissionRequirementsForAllInputDescriptors(\n presentationDefinition.input_descriptors,\n selectResults\n )\n } else {\n presentationSubmission.requirements = getSubmissionRequirements(presentationDefinition, selectResults)\n }\n\n const allEntries = presentationSubmission.requirements.flatMap((requirement) => requirement.submissionEntry)\n\n const inputDescriptorsForMdocCredential = new Map<SubmissionEntryCredential, Set<string>>()\n for (const entry of allEntries)\n for (const verifiableCredential of entry.verifiableCredentials) {\n if (verifiableCredential.claimFormat !== ClaimFormat.MsoMdoc) continue\n\n const set = inputDescriptorsForMdocCredential.get(verifiableCredential) ?? new Set()\n set.add(entry.inputDescriptorId)\n inputDescriptorsForMdocCredential.set(verifiableCredential, set)\n }\n\n // NOTE: it might be better to apply disclosure per credential/match (as that's also how mdoc does this)\n // however this doesn't work very well in wallets, as you usually won't show the same credential twice with\n // different disclosed attributes\n // Apply limit disclosure for all mdocs\n for (const [verifiableCredential, inputDescriptorIds] of inputDescriptorsForMdocCredential.entries()) {\n if (verifiableCredential.claimFormat !== ClaimFormat.MsoMdoc) continue\n\n const inputDescriptorsForCredential = presentationDefinition.input_descriptors.filter(({ id }) =>\n inputDescriptorIds.has(id)\n )\n\n const mdoc = verifiableCredential.credentialRecord.firstCredential\n verifiableCredential.disclosedPayload = await MdocDeviceResponse.limitDisclosureToInputDescriptor(agentContext, {\n inputDescriptor: {\n id: mdoc.docType,\n format: {\n mso_mdoc: {\n alg: [],\n },\n },\n constraints: {\n limit_disclosure: 'required',\n fields: inputDescriptorsForCredential.flatMap((i) => i.constraints?.fields ?? []),\n },\n },\n mdoc,\n })\n }\n\n // There may be no requirements if we filter out all optional ones. To not makes things too complicated, we see it as an error\n // for now if a request is made that has no required requirements (but only e.g. min: 0, which means we don't need to disclose anything)\n // I see this more as the fault of the presentation definition, as it should have at least some requirements.\n if (presentationSubmission.requirements.length === 0) {\n throw new DifPresentationExchangeError(\n 'Presentation Definition does not require any credentials. Optional credentials are not included in the presentation submission.'\n )\n }\n\n if (selectResults.areRequiredCredentialsPresent === Status.ERROR) {\n return presentationSubmission\n }\n\n return {\n ...presentationSubmission,\n\n // If all requirements are satisfied, the presentation submission is satisfied\n areRequirementsSatisfied: presentationSubmission.requirements.every(\n (requirement) => requirement.isRequirementSatisfied\n ),\n }\n}\n\nfunction getSubmissionRequirements(\n presentationDefinition: IPresentationDefinition,\n selectResults: CredentialRecordSelectResults\n): Array<DifPexCredentialsForRequestRequirement> {\n const submissionRequirements: Array<DifPexCredentialsForRequestRequirement> = []\n\n const matches = selectResults.matches as SubmissionRequirementMatchFrom[]\n if (!matches.every((match) => match.type === SubmissionRequirementMatchType.SubmissionRequirement && match.from)) {\n throw new DifPresentationExchangeError(\n `Expected all matches to be of type '${SubmissionRequirementMatchType.SubmissionRequirement}' with 'from' key.`\n )\n }\n\n // There are submission requirements, so we need to select the input_descriptors\n // based on the submission requirements\n presentationDefinition.submission_requirements?.forEach((submissionRequirement, submissionRequirementIndex) => {\n // Check: if the submissionRequirement uses `from_nested`, as we don't support this yet\n if (submissionRequirement.from_nested) {\n throw new DifPresentationExchangeError(\n \"Presentation definition contains requirement using 'from_nested', which is not supported yet.\"\n )\n }\n\n // Check if there's a 'from'. If not the structure is not as we expect it\n if (!submissionRequirement.from) {\n throw new DifPresentationExchangeError(\"Missing 'from' in submission requirement match\")\n }\n\n const match = matches.find((match) => match.id === submissionRequirementIndex)\n if (!match) {\n throw new Error(`Unable to find a match for submission requirement with index '${submissionRequirementIndex}'`)\n }\n\n if (submissionRequirement.rule === Rules.All) {\n const selectedSubmission = getSubmissionRequirementRuleAll(\n submissionRequirement,\n presentationDefinition,\n selectResults.verifiableCredential,\n match\n )\n submissionRequirements.push(selectedSubmission)\n } else {\n const selectedSubmission = getSubmissionRequirementRulePick(\n submissionRequirement,\n presentationDefinition,\n selectResults.verifiableCredential,\n match\n )\n\n submissionRequirements.push(selectedSubmission)\n }\n })\n\n // Submission may have requirement that doesn't require a credential to be submitted (e.g. min: 0)\n // We use minimization strategy, and thus only disclose the minimum amount of information\n const requirementsWithCredentials = submissionRequirements.filter((requirement) => requirement.needsCount > 0)\n\n return requirementsWithCredentials\n}\n\nfunction getSubmissionRequirementsForAllInputDescriptors(\n inputDescriptors: Array<InputDescriptorV1> | Array<InputDescriptorV2>,\n selectResults: CredentialRecordSelectResults\n): Array<DifPexCredentialsForRequestRequirement> {\n const submissionRequirements: Array<DifPexCredentialsForRequestRequirement> = []\n\n const matches = selectResults.matches as SubmissionRequirementMatchInputDescriptor[]\n if (!matches.every((match) => match.type === SubmissionRequirementMatchType.InputDescriptor)) {\n throw new DifPresentationExchangeError(\n `Expected all matches to be of type '${SubmissionRequirementMatchType.InputDescriptor}' when.`\n )\n }\n\n for (const inputDescriptor of inputDescriptors) {\n const submission = getSubmissionForInputDescriptor(inputDescriptor, selectResults.verifiableCredential, matches)\n submissionRequirements.push({\n rule: Rules.Pick,\n needsCount: 1, // Every input descriptor is a distinct requirement, so the count is always 1,\n submissionEntry: [submission],\n isRequirementSatisfied: submission.verifiableCredentials.length >= 1,\n })\n }\n\n return submissionRequirements\n}\n\nfunction getSubmissionRequirementRuleAll(\n submissionRequirement: SubmissionRequirement,\n presentationDefinition: IPresentationDefinition,\n verifiableCredentials: SubmissionEntryCredential[],\n match: SubmissionRequirementMatchFrom\n) {\n // Check if there's a 'from'. If not the structure is not as we expect it\n if (!submissionRequirement.from)\n throw new DifPresentationExchangeError(\"Missing 'from' in submission requirement match.\")\n\n const selectedSubmission: DifPexCredentialsForRequestRequirement = {\n rule: Rules.All,\n needsCount: 0,\n name: submissionRequirement.name,\n purpose: submissionRequirement.purpose,\n submissionEntry: [],\n isRequirementSatisfied: false,\n }\n\n for (const inputDescriptor of presentationDefinition.input_descriptors) {\n // We only want to get the submission if the input descriptor belongs to the group\n if (!inputDescriptor.group?.includes(match.from)) continue\n\n const submission = getSubmissionForInputDescriptor(inputDescriptor, verifiableCredentials, match.input_descriptors)\n\n // Rule ALL, so for every input descriptor that matches in this group, we need to add it\n selectedSubmission.needsCount += 1\n selectedSubmission.submissionEntry.push(submission)\n }\n\n return {\n ...selectedSubmission,\n\n // If all submissions have a credential, the requirement is satisfied\n isRequirementSatisfied: selectedSubmission.submissionEntry.every(\n (submission) => submission.verifiableCredentials.length >= 1\n ),\n }\n}\n\nfunction getSubmissionRequirementRulePick(\n submissionRequirement: SubmissionRequirement,\n presentationDefinition: IPresentationDefinition,\n verifiableCredentials: SubmissionEntryCredential[],\n match: SubmissionRequirementMatchFrom\n) {\n // Check if there's a 'from'. If not the structure is not as we expect it\n if (!submissionRequirement.from) {\n throw new DifPresentationExchangeError(\"Missing 'from' in submission requirement match.\")\n }\n\n const selectedSubmission: DifPexCredentialsForRequestRequirement = {\n rule: Rules.Pick,\n needsCount: submissionRequirement.count ?? submissionRequirement.min ?? 1,\n name: submissionRequirement.name,\n purpose: submissionRequirement.purpose,\n // If there's no count, min, or max we assume one credential is required for submission\n // however, the exact behavior is not specified in the spec\n submissionEntry: [],\n isRequirementSatisfied: false,\n }\n\n const satisfiedSubmissions: Array<DifPexCredentialsForRequestSubmissionEntry> = []\n const unsatisfiedSubmissions: Array<DifPexCredentialsForRequestSubmissionEntry> = []\n\n for (const inputDescriptor of presentationDefinition.input_descriptors) {\n // We only want to get the submission if the input descriptor belongs to the group\n if (!inputDescriptor.group?.includes(match.from)) continue\n\n const submission = getSubmissionForInputDescriptor(inputDescriptor, verifiableCredentials, match.input_descriptors)\n\n if (submission.verifiableCredentials.length >= 1) {\n satisfiedSubmissions.push(submission)\n } else {\n unsatisfiedSubmissions.push(submission)\n }\n\n // If we have found enough credentials to satisfy the requirement, we could stop\n // but the user may not want the first x that match, so we continue and return all matches\n // if (satisfiedSubmissions.length === selectedSubmission.needsCount) break\n }\n\n return {\n ...selectedSubmission,\n\n // If there are enough satisfied submissions, the requirement is satisfied\n isRequirementSatisfied: satisfiedSubmissions.length >= selectedSubmission.needsCount,\n\n // if the requirement is satisfied, we only need to return the satisfied submissions\n // however if the requirement is not satisfied, we include all entries so the wallet could\n // render which credentials are missing.\n submissionEntry:\n satisfiedSubmissions.length >= selectedSubmission.needsCount\n ? satisfiedSubmissions\n : [...satisfiedSubmissions, ...unsatisfiedSubmissions],\n }\n}\n\nfunction getSubmissionForInputDescriptor(\n inputDescriptor: InputDescriptorV1 | InputDescriptorV2,\n verifiableCredentials: SubmissionEntryCredential[],\n matches: SubmissionRequirementMatchInputDescriptor[]\n): DifPexCredentialsForRequestSubmissionEntry {\n const matchesForInputDescriptor = matches.filter((m) => m.id === inputDescriptor.id)\n\n const submissionEntry: DifPexCredentialsForRequestSubmissionEntry = {\n inputDescriptorId: inputDescriptor.id,\n name: inputDescriptor.name,\n purpose: inputDescriptor.purpose,\n verifiableCredentials: matchesForInputDescriptor.flatMap((matchForInputDescriptor) =>\n extractCredentialsFromInputDescriptorMatch(matchForInputDescriptor, verifiableCredentials)\n ),\n }\n\n // return early if no matches.\n if (!matchesForInputDescriptor?.length) return submissionEntry\n\n return submissionEntry\n}\n\nfunction extractCredentialsFromInputDescriptorMatch(\n match: SubmissionRequirementMatchInputDescriptor,\n availableCredentials: SubmissionEntryCredential[]\n) {\n const verifiableCredentials: SubmissionEntryCredential[] = []\n\n for (const vcPath of match.vc_path) {\n const [verifiableCredential] = JSONPath.query(\n { verifiableCredential: availableCredentials },\n vcPath\n ) as SubmissionEntryCredential[]\n verifiableCredentials.push(verifiableCredential)\n }\n\n return verifiableCredentials\n}\n\n/**\n * Custom SelectResults that includes the Credo records instead of the encoded verifiable credential\n */\ntype CredentialRecordSelectResults = Omit<SelectResults, 'verifiableCredential'> & {\n verifiableCredential: SubmissionEntryCredential[]\n matches: SubmissionRequirementMatch[]\n}\n"],"mappings":"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"}