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

191 lines (190 loc) • 10.6 kB
import { CredoError } from "../../../error/CredoError.mjs"; import "../../../error/index.mjs"; import { Hasher } from "../../../crypto/hashes/Hasher.mjs"; import "../../vc/models/ClaimFormat.mjs"; import "../../../crypto/index.mjs"; import { W3cCredentialRecord } from "../../vc/repository/W3cCredentialRecord.mjs"; import "../../vc/index.mjs"; import { MdocRecord } from "../../mdoc/repository/MdocRecord.mjs"; import { SdJwtVcRecord } from "../../sd-jwt-vc/repository/SdJwtVcRecord.mjs"; import { MdocDeviceResponse } from "../../mdoc/MdocDeviceResponse.mjs"; import "../../mdoc/index.mjs"; import "../../sd-jwt-vc/index.mjs"; import { DifPresentationExchangeError } from "../DifPresentationExchangeError.mjs"; import { getSphereonOriginalVerifiableCredential } from "./transform.mjs"; import { decodeSdJwtSync, getClaimsSync } from "@sd-jwt/core"; import { Status } from "@animo-id/pex"; import { SubmissionRequirementMatchType } from "@animo-id/pex/dist/main/lib/evaluation/core/index.js"; import { JSONPath } from "@astronautlabs/jsonpath"; import { Rules } from "@sphereon/pex-models"; //#region src/modules/dif-presentation-exchange/utils/credentialSelection.ts async function getCredentialsForRequest(agentContext, pex, presentationDefinition, credentialRecords) { const encodedCredentials = credentialRecords.map(getSphereonOriginalVerifiableCredential); const selectResultsRaw = pex.selectFrom(presentationDefinition, encodedCredentials); const selectResults = { ...selectResultsRaw, matches: selectResultsRaw.matches ?? [], verifiableCredential: selectResultsRaw.verifiableCredential?.map((selectedEncoded, index) => { const credentialRecordIndex = selectResultsRaw.vcIndexes?.[index]; if (credentialRecordIndex === void 0 || credentialRecordIndex === -1) throw new DifPresentationExchangeError("Unable to find credential in credential records."); const credentialRecord = credentialRecords[credentialRecordIndex]; if (credentialRecord instanceof SdJwtVcRecord) { const { jwt, disclosures } = decodeSdJwtSync(selectedEncoded, Hasher.hash); return { claimFormat: "dc+sd-jwt", credentialRecord, disclosedPayload: getClaimsSync(jwt.payload, disclosures, Hasher.hash) }; } if (credentialRecord instanceof MdocRecord) return { claimFormat: "mso_mdoc", credentialRecord, disclosedPayload: {} }; if (credentialRecord instanceof W3cCredentialRecord) return { claimFormat: credentialRecord.firstCredential.claimFormat, credentialRecord }; throw new CredoError("Unrecognized credential record type"); }) ?? [] }; const presentationSubmission = { requirements: [], areRequirementsSatisfied: false, name: presentationDefinition.name, purpose: presentationDefinition.purpose }; if (!presentationDefinition.submission_requirements || presentationDefinition.submission_requirements.length === 0) presentationSubmission.requirements = getSubmissionRequirementsForAllInputDescriptors(presentationDefinition.input_descriptors, selectResults); else presentationSubmission.requirements = getSubmissionRequirements(presentationDefinition, selectResults); const allEntries = presentationSubmission.requirements.flatMap((requirement) => requirement.submissionEntry); const inputDescriptorsForMdocCredential = /* @__PURE__ */ new Map(); for (const entry of allEntries) for (const verifiableCredential of entry.verifiableCredentials) { if (verifiableCredential.claimFormat !== "mso_mdoc") continue; const set = inputDescriptorsForMdocCredential.get(verifiableCredential) ?? /* @__PURE__ */ new Set(); set.add(entry.inputDescriptorId); inputDescriptorsForMdocCredential.set(verifiableCredential, set); } for (const [verifiableCredential, inputDescriptorIds] of inputDescriptorsForMdocCredential.entries()) { if (verifiableCredential.claimFormat !== "mso_mdoc") continue; const inputDescriptorsForCredential = presentationDefinition.input_descriptors.filter(({ id }) => inputDescriptorIds.has(id)); const mdoc = verifiableCredential.credentialRecord.firstCredential; verifiableCredential.disclosedPayload = await MdocDeviceResponse.limitDisclosureToInputDescriptor(agentContext, { inputDescriptor: { id: mdoc.docType, format: { mso_mdoc: { alg: [] } }, constraints: { limit_disclosure: "required", fields: inputDescriptorsForCredential.flatMap((i) => i.constraints?.fields ?? []) } }, mdoc }); } if (presentationSubmission.requirements.length === 0) throw new DifPresentationExchangeError("Presentation Definition does not require any credentials. Optional credentials are not included in the presentation submission."); if (selectResults.areRequiredCredentialsPresent === Status.ERROR) return presentationSubmission; return { ...presentationSubmission, areRequirementsSatisfied: presentationSubmission.requirements.every((requirement) => requirement.isRequirementSatisfied) }; } function getSubmissionRequirements(presentationDefinition, selectResults) { const submissionRequirements = []; const matches = selectResults.matches; if (!matches.every((match) => match.type === SubmissionRequirementMatchType.SubmissionRequirement && match.from)) throw new DifPresentationExchangeError(`Expected all matches to be of type '${SubmissionRequirementMatchType.SubmissionRequirement}' with 'from' key.`); presentationDefinition.submission_requirements?.forEach((submissionRequirement, submissionRequirementIndex) => { if (submissionRequirement.from_nested) throw new DifPresentationExchangeError("Presentation definition contains requirement using 'from_nested', which is not supported yet."); if (!submissionRequirement.from) throw new DifPresentationExchangeError("Missing 'from' in submission requirement match"); const match = matches.find((match) => match.id === submissionRequirementIndex); if (!match) throw new Error(`Unable to find a match for submission requirement with index '${submissionRequirementIndex}'`); if (submissionRequirement.rule === Rules.All) { const selectedSubmission = getSubmissionRequirementRuleAll(submissionRequirement, presentationDefinition, selectResults.verifiableCredential, match); submissionRequirements.push(selectedSubmission); } else { const selectedSubmission = getSubmissionRequirementRulePick(submissionRequirement, presentationDefinition, selectResults.verifiableCredential, match); submissionRequirements.push(selectedSubmission); } }); return submissionRequirements.filter((requirement) => requirement.needsCount > 0); } function getSubmissionRequirementsForAllInputDescriptors(inputDescriptors, selectResults) { const submissionRequirements = []; const matches = selectResults.matches; if (!matches.every((match) => match.type === SubmissionRequirementMatchType.InputDescriptor)) throw new DifPresentationExchangeError(`Expected all matches to be of type '${SubmissionRequirementMatchType.InputDescriptor}' when.`); for (const inputDescriptor of inputDescriptors) { const submission = getSubmissionForInputDescriptor(inputDescriptor, selectResults.verifiableCredential, matches); submissionRequirements.push({ rule: Rules.Pick, needsCount: 1, submissionEntry: [submission], isRequirementSatisfied: submission.verifiableCredentials.length >= 1 }); } return submissionRequirements; } function getSubmissionRequirementRuleAll(submissionRequirement, presentationDefinition, verifiableCredentials, match) { if (!submissionRequirement.from) throw new DifPresentationExchangeError("Missing 'from' in submission requirement match."); const selectedSubmission = { rule: Rules.All, needsCount: 0, name: submissionRequirement.name, purpose: submissionRequirement.purpose, submissionEntry: [], isRequirementSatisfied: false }; for (const inputDescriptor of presentationDefinition.input_descriptors) { if (!inputDescriptor.group?.includes(match.from)) continue; const submission = getSubmissionForInputDescriptor(inputDescriptor, verifiableCredentials, match.input_descriptors); selectedSubmission.needsCount += 1; selectedSubmission.submissionEntry.push(submission); } return { ...selectedSubmission, isRequirementSatisfied: selectedSubmission.submissionEntry.every((submission) => submission.verifiableCredentials.length >= 1) }; } function getSubmissionRequirementRulePick(submissionRequirement, presentationDefinition, verifiableCredentials, match) { if (!submissionRequirement.from) throw new DifPresentationExchangeError("Missing 'from' in submission requirement match."); const selectedSubmission = { rule: Rules.Pick, needsCount: submissionRequirement.count ?? submissionRequirement.min ?? 1, name: submissionRequirement.name, purpose: submissionRequirement.purpose, submissionEntry: [], isRequirementSatisfied: false }; const satisfiedSubmissions = []; const unsatisfiedSubmissions = []; for (const inputDescriptor of presentationDefinition.input_descriptors) { if (!inputDescriptor.group?.includes(match.from)) continue; const submission = getSubmissionForInputDescriptor(inputDescriptor, verifiableCredentials, match.input_descriptors); if (submission.verifiableCredentials.length >= 1) satisfiedSubmissions.push(submission); else unsatisfiedSubmissions.push(submission); } return { ...selectedSubmission, isRequirementSatisfied: satisfiedSubmissions.length >= selectedSubmission.needsCount, submissionEntry: satisfiedSubmissions.length >= selectedSubmission.needsCount ? satisfiedSubmissions : [...satisfiedSubmissions, ...unsatisfiedSubmissions] }; } function getSubmissionForInputDescriptor(inputDescriptor, verifiableCredentials, matches) { const matchesForInputDescriptor = matches.filter((m) => m.id === inputDescriptor.id); const submissionEntry = { inputDescriptorId: inputDescriptor.id, name: inputDescriptor.name, purpose: inputDescriptor.purpose, verifiableCredentials: matchesForInputDescriptor.flatMap((matchForInputDescriptor) => extractCredentialsFromInputDescriptorMatch(matchForInputDescriptor, verifiableCredentials)) }; if (!matchesForInputDescriptor?.length) return submissionEntry; return submissionEntry; } function extractCredentialsFromInputDescriptorMatch(match, availableCredentials) { const verifiableCredentials = []; for (const vcPath of match.vc_path) { const [verifiableCredential] = JSONPath.query({ verifiableCredential: availableCredentials }, vcPath); verifiableCredentials.push(verifiableCredential); } return verifiableCredentials; } //#endregion export { getCredentialsForRequest }; //# sourceMappingURL=credentialSelection.mjs.map