@animo-id/pex
Version:
A Typescript implementation of the v1 and v2 DIF Presentation Exchange specification
450 lines • 45.8 kB
JavaScript
import { Status } from './ConstraintUtils';
import { EvaluationClientWrapper } from './evaluation';
import { PresentationSubmissionLocation, } from './signing';
import { PEVersion, SSITypesBuilder } from './types';
import { PexCredentialMapper, } from './types/PexCredentialMapper';
import { calculateSdHash, definitionVersionDiscovery, formatValidationErrors, getSubjectIdsAsString } from './utils';
import { PresentationDefinitionV1VB, PresentationDefinitionV2VB, PresentationSubmissionVB, ValidationEngine } from './validation';
/**
* This is the main interfacing class to be used by developers using the PEX library.
*/
export class PEX {
_evaluationClientWrapper;
options;
constructor(options) {
// TODO: So we have state in the form of this property which is set in the constructor, but we are overwriting it elsewhere. We need to retrhink how to instantiate PEX
this._evaluationClientWrapper = new EvaluationClientWrapper();
this.options = options;
}
/***
* The evaluatePresentation compares what is expected from one or more presentations with a presentationDefinition.
* presentationDefinition: It can be either v1 or v2 of presentationDefinition
*
* @param presentationDefinition the definition of what is expected in the presentation.
* @param presentations the presentation(s) which have to be evaluated in comparison of the definition.
* @param opts - limitDisclosureSignatureSuites the credential signature suites that support limit disclosure
*
* @return the evaluation results specify what was expected and was fulfilled and also specifies which requirements described in the input descriptors
* were not fulfilled by the presentation(s).
*/
evaluatePresentation(presentationDefinition, presentations, opts) {
// We map it to an array for now to make processing on the presentations easier, but before checking against the submission
// we will transform it to the original structure (array vs single) so the references in the submission stay correct
const presentationsArray = Array.isArray(presentations) ? presentations : [presentations];
if (presentationsArray.length === 0) {
throw new Error('At least one presentation must be provided');
}
let originalPresentationSubmission = opts?.presentationSubmission;
const generatePresentationSubmission = opts?.generatePresentationSubmission !== undefined ? opts.generatePresentationSubmission : opts?.presentationSubmission === undefined;
const pd = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition);
const presentationsCopy = JSON.parse(JSON.stringify(presentationsArray));
const wrappedPresentations = presentationsCopy.map((p) => SSITypesBuilder.mapExternalVerifiablePresentationToWrappedVP(p));
let presentationSubmission = opts?.presentationSubmission;
let presentationSubmissionLocation = opts?.presentationSubmissionLocation ??
((Array.isArray(presentations) && presentations.length > 1) || !PexCredentialMapper.isW3cPresentation(wrappedPresentations[0].presentation)
? PresentationSubmissionLocation.EXTERNAL
: PresentationSubmissionLocation.PRESENTATION);
// When only one presentation, we also allow it to be present in the VP
if (!presentationSubmission &&
presentationsArray.length === 1 &&
PexCredentialMapper.isW3cPresentation(wrappedPresentations[0].presentation) &&
!opts?.generatePresentationSubmission) {
const decoded = wrappedPresentations[0].decoded;
if ('presentation_submission' in decoded) {
presentationSubmission = JSON.parse(JSON.stringify(decoded.presentation_submission));
originalPresentationSubmission = decoded.presentation_submission;
}
if (!presentationSubmission) {
throw Error(`Either a presentation submission as part of the VP or provided in options was expected`);
}
presentationSubmissionLocation = PresentationSubmissionLocation.PRESENTATION;
if (opts?.presentationSubmissionLocation && opts.presentationSubmissionLocation !== PresentationSubmissionLocation.PRESENTATION) {
throw new Error(`unexpected presentationSubmissionLocation ${opts.presentationSubmissionLocation} was provided. Expected ${PresentationSubmissionLocation.PRESENTATION} when no presentationSubmission passed and first verifiable presentation contains a presentation_submission and generatePresentationSubmission is false`);
}
// We need to update the vp path as PEX decoded assumes it's an external submission
// So we need to update the submission paths
if (wrappedPresentations[0].format === 'jwt_vp') {
for (const descriptor of presentationSubmission.descriptor_map) {
if (!descriptor.path.startsWith('$.vp')) {
descriptor.path = descriptor.path.replace('$.', '$.vp.');
}
}
}
}
else if (!presentationSubmission && !generatePresentationSubmission) {
throw new Error('Presentation submission in options was expected.');
}
// TODO: we should probably add support for holder dids in the kb-jwt of an SD-JWT. We can extract this from the
// `wrappedPresentation.original.compactKbJwt`, but as HAIP doesn't use dids, we'll leave it for now.
const holderDIDs = wrappedPresentations
.map((p) => {
return PexCredentialMapper.isW3cPresentation(p.presentation) && p.presentation.holder ? p.presentation.holder : undefined;
})
.filter((d) => d !== undefined);
const updatedOpts = {
...opts,
holderDIDs,
presentationSubmission,
presentationSubmissionLocation,
generatePresentationSubmission,
};
const allWvcs = wrappedPresentations.reduce((all, wvp) => [...all, ...wvp.vcs], []);
const result = this._evaluationClientWrapper.evaluatePresentations(pd, Array.isArray(presentations) ? wrappedPresentations : wrappedPresentations[0], updatedOpts);
if (result.areRequiredCredentialsPresent !== Status.ERROR) {
const selectFromClientWrapper = new EvaluationClientWrapper();
const selectResults = selectFromClientWrapper.selectFrom(pd, allWvcs, updatedOpts);
if (selectResults.areRequiredCredentialsPresent !== Status.ERROR) {
result.errors = [];
}
}
return {
...result,
value: originalPresentationSubmission ?? result.value,
};
}
/***
* The evaluate compares what is expected from a verifiableCredentials with the presentationDefinition.
*
* @param presentationDefinition the v1 or v2 definition of what is expected in the presentation.
* @param verifiableCredentials the verifiable credentials which are candidates to fulfill requirements defined in the presentationDefinition param.
* @param opts - holderDIDs the list of the DIDs that the wallet holders controls. Optional, but needed by some input requirements that do a holderDID check.
* @ - limitDisclosureSignatureSuites the credential signature suites that support limit disclosure
*
* @return the evaluation results specify what was expected and was fulfilled and also specifies which requirements described in the input descriptors
* were not fulfilled by the verifiable credentials.
*/
evaluateCredentials(presentationDefinition, verifiableCredentials, opts) {
const wrappedVerifiableCredentials = SSITypesBuilder.mapExternalVerifiableCredentialsToWrappedVcs(verifiableCredentials);
// TODO: So we have state in the form of this property which is set in the constructor, but we are overwriting it here. We need to retrhink how to instantiate PEX
this._evaluationClientWrapper = new EvaluationClientWrapper();
const pd = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition);
const result = this._evaluationClientWrapper.evaluate(pd, wrappedVerifiableCredentials, opts);
if (result.value && result.value.descriptor_map.length) {
const selectFromClientWrapper = new EvaluationClientWrapper();
const selectResults = selectFromClientWrapper.selectFrom(pd, wrappedVerifiableCredentials, opts);
result.areRequiredCredentialsPresent = selectResults.areRequiredCredentialsPresent;
result.errors = selectResults.errors;
}
else {
result.areRequiredCredentialsPresent = Status.ERROR;
}
return result;
}
/**
* The selectFrom method is a helper function that helps filter out the verifiable credentials which can not be selected and returns
* the selectable credentials.
*
* @param presentationDefinition the v1 or v2 definition of what is expected in the presentation.
* @param verifiableCredentials verifiable credentials are the credentials from wallet provided to the library to find selectable credentials.
* @param opts - holderDIDs the decentralized identifier(s) of the wallet holderDID. This is used to identify the credentials issued to the holderDID of wallet in certain scenario's.
* - limitDisclosureSignatureSuites the credential signature suites that support limit disclosure
*
* @return the selectable credentials.
*/
selectFrom(presentationDefinition, verifiableCredentials, opts) {
const verifiableCredentialCopy = JSON.parse(JSON.stringify(verifiableCredentials));
const pd = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition);
// TODO: So we have state in the form of this property which is set in the constructor, but we are overwriting it here. We need to retrhink how to instantiate PEX
this._evaluationClientWrapper = new EvaluationClientWrapper();
return this._evaluationClientWrapper.selectFrom(pd, SSITypesBuilder.mapExternalVerifiableCredentialsToWrappedVcs(verifiableCredentialCopy), opts);
}
presentationSubmissionFrom(presentationDefinition, selectedCredentials, opts) {
const pd = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition);
return this._evaluationClientWrapper.submissionFrom(pd, SSITypesBuilder.mapExternalVerifiableCredentialsToWrappedVcs(selectedCredentials), opts);
}
/**
* This method helps create an Unsigned Presentation. An Unsigned Presentation after signing becomes a Presentation. And can be sent to
* the verifier after signing it.
*
* @param presentationDefinition the v1 or v2 definition of what is expected in the presentation.
* @param selectedCredentials the credentials which were declared selectable by getSelectableCredentials and then chosen by the intelligent-user
* (e.g. human).
* @param opts - holderDID optional; the decentralized identity of the wallet holderDID. This is used to identify the holderDID of the presentation.
*
* @return the presentation.
*/
presentationFrom(presentationDefinition, selectedCredentials, opts) {
const presentationSubmission = this.presentationSubmissionFrom(presentationDefinition, selectedCredentials, opts);
const hasSdJwtCredentials = selectedCredentials.some((c) => PexCredentialMapper.isSdJwtDecodedCredential(c) || PexCredentialMapper.isSdJwtEncoded(c));
// We could include it in the KB-JWT? Not sure if we want that
if (opts?.presentationSubmissionLocation === PresentationSubmissionLocation.PRESENTATION && hasSdJwtCredentials) {
throw new Error('Presentation submission location cannot be set to presentation when creating a presentation with an SD-JWT VC');
}
const presentationSubmissionLocation = opts?.presentationSubmissionLocation ??
(hasSdJwtCredentials ? PresentationSubmissionLocation.EXTERNAL : PresentationSubmissionLocation.PRESENTATION);
const presentations = this.constructPresentations(selectedCredentials, {
...opts,
// We only pass in the submission in case it needs to be included in the presentation
presentationSubmission: presentationSubmissionLocation === PresentationSubmissionLocation.PRESENTATION ? presentationSubmission : undefined,
});
this.updateSdJwtCredentials(presentations);
return {
presentations,
presentationSubmissionLocation,
presentationSubmission,
};
}
constructPresentations(selectedCredentials, opts) {
if (!selectedCredentials) {
throw Error(`At least a verifiable credential needs to be passed in to create a presentation`);
}
const verifiableCredential = (Array.isArray(selectedCredentials) ? selectedCredentials : [selectedCredentials]);
const wVCs = verifiableCredential.map((vc) => PexCredentialMapper.toWrappedVerifiableCredential(vc));
const holders = Array.from(new Set(wVCs.flatMap((wvc) => getSubjectIdsAsString(wvc.credential))));
const holder = opts?.holderDID ?? (holders.length === 1 ? holders[0] : undefined);
const type = opts?.basePresentationPayload?.type
? Array.isArray(opts.basePresentationPayload.type)
? opts.basePresentationPayload.type
: [opts.basePresentationPayload.type]
: [];
if (!type.includes('VerifiablePresentation')) {
type.push('VerifiablePresentation');
}
const context = opts?.basePresentationPayload?.['@context']
? Array.isArray(opts.basePresentationPayload['@context'])
? opts.basePresentationPayload['@context']
: [opts.basePresentationPayload['@context']]
: [];
if (!context.includes('https://www.w3.org/2018/credentials/v1')) {
context.push('https://www.w3.org/2018/credentials/v1');
}
if (opts?.presentationSubmission) {
if (!type.includes('PresentationSubmission')) {
type.push('PresentationSubmission');
}
if (!context.includes('https://identity.foundation/presentation-exchange/submission/v1')) {
context.push('https://identity.foundation/presentation-exchange/submission/v1');
}
}
const result = [];
if (PEX.allowMultipleVCsPerPresentation(verifiableCredential)) {
result.push({
...opts?.basePresentationPayload,
'@context': context,
type,
holder,
...(!!opts?.presentationSubmission && { presentation_submission: opts.presentationSubmission }),
verifiableCredential,
});
}
else {
verifiableCredential.forEach((vc) => {
if (PexCredentialMapper.isSdJwtDecodedCredential(vc)) {
result.push(vc);
}
else if (PexCredentialMapper.isSdJwtEncoded(vc)) {
const decoded = PexCredentialMapper.decodeVerifiableCredential(vc);
result.push(decoded);
}
else {
// This should be jwt or json-ld
result.push({
...opts?.basePresentationPayload,
'@context': context,
type,
holder,
...(!!opts?.presentationSubmission && { presentation_submission: opts.presentationSubmission }),
verifiableCredential: [vc],
});
}
});
}
return result;
}
/*
TODO SDK-37 refinement needed
*/
static allowMultipleVCsPerPresentation(verifiableCredentials) {
const jwtCredentials = verifiableCredentials.filter((c) => PexCredentialMapper.isJwtEncoded(c) || PexCredentialMapper.isJwtDecodedCredential(c));
if (jwtCredentials.length > 0) {
const subjects = new Set();
const verificationMethods = new Set();
for (const credential of jwtCredentials) {
const decodedCredential = PexCredentialMapper.isJwtEncoded(credential)
? PexCredentialMapper.decodeVerifiableCredential(credential)
: credential;
const subject = decodedCredential.sub ||
(decodedCredential.vc && 'id' in decodedCredential.vc.credentialSubject && decodedCredential.vc.credentialSubject.id);
if (subject) {
subjects.add(subject);
}
const vcProof = decodedCredential.proof ?? decodedCredential.vc.proof;
const proofs = Array.isArray(vcProof) ? vcProof : [vcProof];
proofs.filter((proof) => proof.verificationMethod).forEach((proof) => verificationMethods.add(proof.verificationMethod));
}
// If there's more than one unique subject or verification method, we can't allow multiple VCs in a single presentation
if (subjects.size > 1 || verificationMethods.size > 1) {
return false;
}
}
if (verifiableCredentials.some((c) => PexCredentialMapper.isSdJwtEncoded(c) || PexCredentialMapper.isSdJwtDecodedCredential(c))) {
return false;
}
return true;
}
/**
* This method validates whether an object is usable as a presentation definition or not.
*
* @param presentationDefinition presentationDefinition of V1 or v2 to be validated.
*
* @return the validation results to reveal what is acceptable/unacceptable about the passed object to be considered a valid presentation definition
*/
static validateDefinition(presentationDefinition) {
const result = definitionVersionDiscovery(presentationDefinition);
if (result.error) {
const errorParts = [result.error];
const v1ErrorString = formatValidationErrors(result.v1Errors);
if (v1ErrorString) {
errorParts.push('\nVersion 1 validation errors:\n ' + v1ErrorString);
}
const v2ErrorString = formatValidationErrors(result.v2Errors);
if (v2ErrorString) {
errorParts.push('\nVersion 2 validation errors:\n ' + v2ErrorString);
}
throw new Error(errorParts.join(''));
}
const validators = [];
result.version === PEVersion.v1
? validators.push({
bundler: new PresentationDefinitionV1VB('root'),
target: SSITypesBuilder.modelEntityToInternalPresentationDefinitionV1(presentationDefinition),
})
: validators.push({
bundler: new PresentationDefinitionV2VB('root'),
target: SSITypesBuilder.modelEntityInternalPresentationDefinitionV2(presentationDefinition),
});
return new ValidationEngine().validate(validators);
}
/**
* This method validates whether an object is usable as a presentation submission or not.
*
* @param presentationSubmission the object to be validated.
*
* @return the validation results to reveal what is acceptable/unacceptable about the passed object to be considered a valid presentation submission
*/
static validateSubmission(presentationSubmission) {
return new ValidationEngine().validate([
{
bundler: new PresentationSubmissionVB('root'),
target: presentationSubmission,
},
]);
}
/**
* This method can be used to combine a definition, selected Verifiable Credentials, together with
* signing opts and a callback to sign a presentation, making it a Verifiable Presentation before sending.
*
* Please note that PEX has no signature support on purpose. We didn't want this library to depend on all kinds of signature suites.
* The callback function next to the Signing Params also gets a Presentation which is evaluated against the definition.
* It is up to you to decide whether you simply update the supplied partial proof and add it to the presentation in the callback,
* or whether you will use the selected Credentials, Presentation definition, evaluation results and/or presentation submission together with the signature opts
*
* @param presentationDefinition the Presentation Definition V1 or V2
* @param selectedCredentials the PEX and/or User selected/filtered credentials that will become part of the Verifiable Presentation
* @param signingCallBack the function which will be provided as a parameter. And this will be the method that will be able to perform actual
* signing. One example of signing is available in the project named. pe-selective-disclosure.
* @param opts Signing Params these are the signing params required to sign.
*
* @return the signed and thus Verifiable Presentation.
*/
async verifiablePresentationFrom(presentationDefinition, selectedCredentials, signingCallBack, opts) {
const { holderDID, signatureOptions, proofOptions } = opts;
function limitedDisclosureSuites() {
let limitDisclosureSignatureSuites = [];
if (proofOptions?.typeSupportsSelectiveDisclosure) {
if (!proofOptions?.type) {
throw Error('Please provide a proof type if you enable selective disclosure');
}
limitDisclosureSignatureSuites = [proofOptions.type];
}
return limitDisclosureSignatureSuites;
}
const holderDIDs = holderDID ? [holderDID] : [];
const limitDisclosureSignatureSuites = limitedDisclosureSuites();
const evaluationResult = this.evaluateCredentials(presentationDefinition, selectedCredentials, {
holderDIDs,
limitDisclosureSignatureSuites,
});
if (evaluationResult.areRequiredCredentialsPresent === Status.ERROR) {
throw new Error('Could not create presentation, selected credentials do not satisfy the presentation definition');
}
const presentationResult = this.presentationFrom(presentationDefinition, evaluationResult.verifiableCredential, opts);
const presentations = presentationResult.presentations;
const evaluationResults = this.evaluatePresentation(presentationDefinition, presentations, {
limitDisclosureSignatureSuites,
...(presentationResult.presentationSubmissionLocation === PresentationSubmissionLocation.EXTERNAL && {
presentationSubmission: presentationResult.presentationSubmission,
}),
});
if (!evaluationResults.value && selectedCredentials.length === 0) {
evaluationResults.value = presentationResult.presentationSubmission;
}
if (!evaluationResults.value) {
throw new Error('Could not get evaluation results from presentationResult');
}
const proof = {
type: proofOptions?.type,
verificationMethod: signatureOptions?.verificationMethod,
created: proofOptions?.created ? proofOptions.created : new Date().toISOString(),
proofPurpose: proofOptions?.proofPurpose,
proofValue: signatureOptions?.proofValue,
jws: signatureOptions?.jws,
challenge: proofOptions?.challenge,
nonce: proofOptions?.nonce,
domain: proofOptions?.domain,
};
this.updateSdJwtCredentials(presentations, proofOptions?.nonce);
const verifiablePresentations = [];
for (const presentation of presentations) {
const callBackParams = {
options: {
...opts,
presentationSubmissionLocation: presentationResult.presentationSubmissionLocation,
},
presentation,
presentationDefinition,
selectedCredentials,
proof,
presentationSubmission: evaluationResults.value,
evaluationResults,
};
verifiablePresentations.push(await signingCallBack(callBackParams));
}
return {
verifiablePresentations,
presentationSubmissionLocation: presentationResult.presentationSubmissionLocation,
presentationSubmission: evaluationResults.value,
};
}
updateSdJwtCredentials(presentations, nonce) {
presentations.forEach((presentation, index) => {
// Select type without kbJwt as isSdJwtDecodedCredential and won't accept the partial sdvc type
if (PexCredentialMapper.isSdJwtDecodedCredential(presentation)) {
const sdJwtCredential = presentation;
// extract sd_alg or default to sha-256
const hashAlg = sdJwtCredential.signedPayload._sd_alg ?? 'sha-256';
const sdHash = calculateSdHash(sdJwtCredential.compactSdJwtVc, hashAlg);
const kbJwt = {
// alg MUST be set by the signer
header: {
typ: 'kb+jwt',
},
// aud MUST be set by the signer or provided by e.g. SIOP/OpenID4VP lib
payload: {
iat: Math.floor(new Date().getTime() / 1000),
nonce: nonce,
sd_hash: sdHash,
},
};
presentations[index] = {
...sdJwtCredential,
kbJwt,
};
}
});
}
static definitionVersionDiscovery(presentationDefinition) {
return definitionVersionDiscovery(presentationDefinition);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUEVYLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vbGliL1BFWC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFjQSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDM0MsT0FBTyxFQUFFLHVCQUF1QixFQUFtRSxNQUFNLGNBQWMsQ0FBQztBQUN4SCxPQUFPLEVBTUwsOEJBQThCLEdBRy9CLE1BQU0sV0FBVyxDQUFDO0FBQ25CLE9BQU8sRUFBd0YsU0FBUyxFQUFFLGVBQWUsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUMzSSxPQUFPLEVBR0wsbUJBQW1CLEdBR3BCLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxFQUFFLGVBQWUsRUFBRSwwQkFBMEIsRUFBRSxzQkFBc0IsRUFBRSxxQkFBcUIsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNySCxPQUFPLEVBQUUsMEJBQTBCLEVBQUUsMEJBQTBCLEVBQUUsd0JBQXdCLEVBQWEsZ0JBQWdCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFjN0k7O0dBRUc7QUFDSCxNQUFNLE9BQU8sR0FBRztJQUNKLHdCQUF3QixDQUEwQjtJQUNsRCxPQUFPLENBQWM7SUFFL0IsWUFBWSxPQUFvQjtRQUM5Qix3S0FBd0s7UUFDeEssSUFBSSxDQUFDLHdCQUF3QixHQUFHLElBQUksdUJBQXVCLEVBQUUsQ0FBQztRQUU5RCxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztJQUN6QixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNJLG9CQUFvQixDQUN6QixzQkFBK0MsRUFDL0MsYUFBZ0gsRUFDaEgsSUFZQztRQUVELDJIQUEySDtRQUMzSCxvSEFBb0g7UUFDcEgsTUFBTSxrQkFBa0IsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDMUYsSUFBSSxrQkFBa0IsQ0FB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