JSPM

@sphereon/pex

0.6.2-unstable.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 7081
  • Score
    100M100P100Q134116F
  • License Apache-2.0

A Typescript implementation of the v1 and v2 DIF Presentation Exchange specification

Package Exports

  • @sphereon/pex
  • @sphereon/pex/dist/browser/index.js
  • @sphereon/pex/dist/main/index.js
  • @sphereon/pex/dist/module/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@sphereon/pex) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme


Sphereon
IPresentation Exchange v1 and v2
Type/JavaScript Library

CI codecov NPM Version

Active Development

IMPORTANT: This software still is in development stage. Although the API has become more stable as of v0.6.0, you should expect breaking changes in APIs without notice, we expect to keep that to a minimum though.

Breaking change: class and package renamed in v0.6.0!

As part of introducing IPresentation Exchange v1 and v2 feature based detection support to our IPresentation Exchange library and not reaching version 1.X yet, we decided to change the name of both the package and the main entry class:

  • The package was changed from @sphereon/pe-js to @sphereon/pex
  • The main class was changed from PEJS to PEX. The latter class has internal feature detection support on the provided definition, delegating the actual implementation to the new PEXv1 or PEXv2 class internally. If you don't want the automatic feature detection you can also choose to use the PEXv1 and PEXv2 classes directly.

Background

The IPresentation Exchange (PEX) Library is a general use presentation exchange library that implements the functionality described in the DIF IPresentation Exchange specification for both version 1.0.0 and 2.0.0. It is written in Typescript and can be compiled to any target JavaScript version.

Sphereon's PEX Library is useful for both verifier systems and holders (e.g. wallets) and can be used in client side browsers and mobile applications as well as on server side technology such as REST APIs (e.g. built with NodeJS). It allows anyone to add DIF IPresentation Exchange logic to their existing wallets, agents and/or verifiers, without making any further assumptions about the technologies used in their products.

A IPresentation Exchange operates generally as follows; The verifier creates a IPresentation Definition asking for credentials from the holder. The definition for the credentials is sent to the holder, who returns a Verifiable IPresentation as a response. Now the verifier will verify the presentation by checking the signature and other accompanying proofs.

The IPresentation Exchange will ensure that the model used by the verifier, can be interpreted by the holder. It then ensures that the correct parts from the holders credentials are used to create the presentation. The PEX contains all the logic to interpret the models, therefore removing the need for the verifier and holder to align their specific models.

The data objects (models) used in PEX are generated from Sphereon's DIF PEX OpenAPI Spec component. The code for the component can be seen at PEX-OpenAPI github repository. This allows the generation of the objects in many languages and frameworks consistently by configuring the maven plugin.

The PEX Library supports the following actions:

  • Creating a presentation definition / request
  • Validating a presentation definition / conforming to the specification v1 and v2
  • Creating a IPresentation
  • Creating a Verifiable IPresentation using a callback function
  • Validating a presentation (submission) when received
  • Input evaluations: Verification of presentation submissions conforming to the presentation definition
  • Utilities: to build and use different models compliant with the DIF IPresentation Exchange v2.0.0 specification.
  • Support for DIF IPresentation Exchange v1.0.0 specification.

Stateful storage, signature support or credential management should be implemented in separate libraries/modules that make use of the underlying DIF IPresentation Exchange implementation. By keeping these separate, the library will stay platform-agnostic and lean regarding dependencies.

For PEX Users

The library can be installed direction from npmjs via:

# install via yarn
  yarn add @sphereon/pex

# install via npm
  npm install @sphereon/pex

The core functionality of the DIF IPresentation Exchange can be outlined as follows:

  • Input Evaluation
  • ICredential Query
  • IPresentation and Verifiable IPresentation creation
  • Utilities

Input Evaluation

Input evaluation is the primary mechanism by which a verifier determines whether a presentation submission from a holder matches the requested presentation definition from the request.

import { PEX } from '@sphereon/pex';

const pex: PEX = new PEX();

// Example of IPresentation Definition V1
const presentationDefinitionV1 = {
  "id": "32f54163-7166-48f1-93d8-ff217bdb0653",
  "input_descriptors": [
    {
      "id": "wa_driver_license",
      "name": "Washington State Business License",
      "purpose": "We can only allow licensed Washington State business representatives into the WA Business Conference",
      "schema": [{
        "uri": "https://licenses.example.com/business-license.json"
      }]
    }
  ]
};

// Example of IPresentation Definition V2
const presentationDefinitionV2 = {
  "id": "32f54163-7166-48f1-93d8-ff217bdb0653",
  "input_descriptors": [
    {
      "id": "wa_driver_license",
      "name": "Washington State Business License",
      "purpose": "We can only allow licensed Washington State business representatives into the WA Business Conference"
    }
  ]
};

const verifiablePresentation = {
  '@context': [
    "https://www.w3.org/2018/credentials/v1",
    "https://identity.foundation/presentation-exchange/submission/v1"
  ],
  type: [
    "IVerifiablePresentation",
    "PresentationSubmission"
  ],
  presentation_submission: { ... },
  verifiableCredential: [...],
  proof: { ... }
}

// We are using the PEX class, to automatically detect the definition version. PEXv1 and PEXv2 are also available to use fixed PEX versions
const { value, warnings, errors } = pex.evaluate(presentationDefinitionV2, verifiablePresentation);

ICredential Query

A credential query allows holders to filter their set of credentials for matches to a given presentation definition.

import { PEX } from '@sphereon/pex';
import { IVerifiableCredential } from "./SSI.types";

const pex: PEX = new PEX();

// Definition from verifier request
const presentationDefinition = {
  ...
};
// Finding out which version presentationDefinition is this:
// The result is either 'v1', 'v2' or an error
// You only have to do this if you want to apply some custom logic. The PEX class uses feature detection on the definition to determine whether it is v1 or v2 internally
const result = pex.definitionVersionDiscovery(pdSchema);

// Example for loading credentials from your secure storage
const credentials: IVerifiableCredential[] = await secureStore.getCredentials();

// Find matching credentials
const srMatches = pex.selectFrom(presentationDefinition, credentials, holderDid);

// An example that selects the first 'count' credentials from
// the matches. in a real scenario, the user has to select which
// credentials to use. PEX did the first filtering,
// but there still could be multiple credentials satisfying a presentation definition
const selectedCredentials = srMatches.map(
  ({ matches, count }) => matches.slice(0, count)
).flat();

IPresentation creation (non verifiable)

To create a IPresentation without IProof (for Proofs, see Verifiable IPresentation below) you have to pass in the IPresentation Definition, selected Verifiable Credentials and an optional holder (DID). The result will be a Verifiable IPresentation, without proofs, so actually a IPresentation. It also contains the presentation submission data that the verifier can use.

It is left up to you to sign the IPresentation and adding the proof and make it a truly Verifiable IPresentation. There are different libraries that allow you to do this. You can also use the callback integration mentioned in the next chapter for this.

import { PEX, IPresentation } from '@sphereon/pex';

const pex: PEX = new PEX();

// Construct presentation from selected credentials
const presentation: IPresentation = pex.presentationFrom(presentationDefinition, selectedCredentials, holderDID);
/** presentation object:
 *
 *   {
 *     "@context": [
 *       "https://www.w3.org/2018/credentials/v1",
 *       "https://identity.foundation/presentation-exchange/submission/v1"
 *     ],
 *     "type": [
 *       "IVerifiablePresentation",
 *       "PresentationSubmission"
 *     ],
 *     presentation_submission: presentationSubmission,
 *     verifiableCredential: selectedCredentials
 *   };
 */
// IPresentation would need to be signed and sent to verifier

Verifiable IPresentation with callback

NOTE: PEX does not support the creation of signatures by itself. That has to do with the fact that we didn't want to rely on all kinds of signature suites and libraries. PEX has minimal dependencies currently, so that it can be used in all kinds of scenarios.

How did we solve this? We have created a callback mechanism, allowing you to create a callback function that gets all input allowing you to use your library of choice to create the signature. The callback needs to accept a PresentationSignCallBackParams object.

The method verifiablePresentationFrom accepts the presentation definition and selected Verifiable Credentials as the first two arguments, just like the presentationFrom method. Next it accepts the callback function as argument and a PresentationSignOptions object as last argument. The sign callback params, allow you to control the signature process. You will have access in the callback to these params as well.

Before calling your callback function a few things happen. First of all, just like the presentationFrom method, it will evaluate whether the supplied credentials conform to the supplied presentation definition. Then it creates a presentation, just like presentationFrom. This presentation is provided for your convenience and can be used in your callback for simple use cases. In more elaborate cases, like for instance with more complex signature suites and/or selective disclosure, you will probably not use the IPresentation directly and make use of other arguments passed into the callback, like the EvaluationResults, PresentationSubmission and Partial<IProof>.

The proofOptions and signatureOptions, allow you to populate proof values directly. in which case the Partial<IProof> will have all fields filled to just add it as a proof to the presentation in your callback. This does mean you would have to create the IPresentation first and sign that, which means you probably have no use for the callback. If you do not provide these values, the Partial<IProof>, will still be populated without the proofValue and jws, based upon your options.

IPresentation Sign Options

The options accepted by the verifiablePresentationFrom are:

interface PresentationSignOptions {
  /**
   * The optional holder of the presentation
   */
  holder?: string;

  /**
   * IProof options
   */
  proofOptions?: ProofOptions;

  /**
   * The signature options
   */
  signatureOptions?: SignatureOptions;
}

interface ProofOptions {
  /**
   * The signature type. For instance RsaSignature2018
   */
  type?: ProofType | string;

  /**
   * Type supports selective disclosure?
   */
  typeSupportsSelectiveDisclosure?: boolean;

  /**
   * A challenge protecting against replay attacks
   */
  challenge?: string;

  /**
   * A domain protecting against replay attacks
   */
  domain?: string;

  /**
   * The purpose of this proof, for instance assertionMethod or authentication, see https://www.w3.org/TR/vc-data-model/#proofs-signatures-0
   */
  proofPurpose?: ProofPurpose | string;

  /**
   * The ISO8601 date-time string for creation. You can update the IProof value later in the callback. If not supplied the current date/time will be used
   */
  created?: string;

  /**
   * Similar to challenge. A nonce to protect against replay attacks, used in some ZKP proofs
   */
  nonce?: string;
}

interface SignatureOptions {
  /**
   * The private key
   */
  privateKey?: string;

  /**
   * Key encoding
   */
  keyEncoding?: KeyEncoding;

  /**
   * The verification method value
   */
  verificationMethod?: string;

  /**
   * Can be used if you want to provide the Json-ld proof value directly without relying on the callback function generating it
   */
  proofValue?: string; // One of any number of valid representations of proof values

  /**
   * Can be used if you want to provide the JSW proof value directly without relying on the callback function generating it
   */
  jws?: string; // JWS based proof
}

These options are available in your callback function by accessing the options field in the PresentationSignCallBackParams.

Callback params object

The callback params gets supplied as the single argument to your callback function. It contains the IPresentation, a partial 'IProof' typically missing the proofValue/jws signature. It also contains the initially supplied Verifiable Credentials and IPresentation Definition as well as your supplied options.

If contains the IPresentation Submission object, which is also found in the presentation. You can use this to create your own IPresentation object if you want. Lastly it contains the evaluation results, which includes the mappings and logs about the evaluation.

You can either choose to use the IPresentation and partial IProof together with the options, or in more elaborate use cases opt to use the PresentationSubmission, EvaluationResults and the options for instance.

export interface PresentationSignCallBackParams {
  /**
   * The originally supplied presentation sign options
   */
  options: PresentationSignOptions;

  /**
   * The presentation definition
   */
  presentationDefinition: PresentationDefinition;

  /**
   * The selected credentials to include in the eventual VP as determined by PEX and/or user
   */
  selectedCredentials: IVerifiableCredential[];

  /**
   * The presentation object created from the definition and verifiable credentials.
   * Can be used directly or in more complex situations can be discarded by using the definition, credentials, proof options, submission and evaluation results
   */
  presentation: IPresentation;

  /**
   * A partial proof value the callback can use to complete. If proofValue or JWS was supplied the proof could be complete already
   */
  proof: Partial<IProof>;

  /**
   * The presentation submission data, which can also be found in the presentation itself
   */
  presentationSubmission: PresentationSubmission;

  /**
   * The evaluation results, which the callback function could use to create a VP using the proof(s) using the supplied credentials
   */
  evaluationResults: EvaluationResults;
}

Simple example of the callback function

A simple use case using your library of choice for non-selective disclosure using an ed25519 key and signature.

import {
  PEX,
  IProof,
  ProofPurpose,
  ProofType,
  IVerifiablePresentation,
  PresentationSignOptions,
  KeyEncoding,
} from '@sphereon/pex';

const pex: PEX = new PEX();

const params: PresentationSignOptions = {
  holder: 'did:example:1234....',
  proofOptions: {
    type: ProofType.Ed25519Signature2018,
    proofPurpose: ProofPurpose.assertionMethod,
  },
  signatureOptions: {
    verificationMethod: 'did:example:"1234......#key',
    keyEncoding: KeyEncoding.Base58,
    privateKey: 'base58 (key encoding type) key here',
  },
};

const vp = pex.verifiablePresentationFrom(
  presentationDefinition,
  selectedCredentials,
  simpleSignedProofCallback,
  params
);

function simpleSignedProofCallback(callBackParams: PresentationSignCallBackParams): IVerifiablePresentation {
  // Prereq is properly filled out `proofOptions` and `signatureOptions`, together with a `proofValue` or `jws` value.
  // And thus a generated signature
  const { presentation, proof, options } = callBackParams; // The created partial proof and presentation, as well as original supplied options
  const { signatureOptions, proofOptions } = options; // extract the orignially supploed signature and proof Options
  const privateKeyBase58 = signatureOptions.privateKey; // Please check keyEncoding from signatureOptions first!

  /**
   * IProof looks like this:
   * {
   *    type: 'Ed25519Signature2018',
   *    created: '2021-12-01T20:10:45.000Z',
   *    proofPurpose: 'assertionMethod',
   *    verificationMethod: 'did:example:"1234......#key',
   *    .....
   * }
   */

  // Just an example. Obviously your lib will have a different method signature
  const vp = myVPSignLibrary(presentation, { ...proof, privateKeyBase58 });

  return vp;
}

Utilities

In addition to the core functionality above, the underlying validation methods are exposed as low-level helper functions.

import { PEX } from '@sphereon/pex';

const pex: PEX = new PEX();

const presentationDefinition = {
  ...
};

const result = pex.definitionVersionDiscovery(presentationDefinition);
const { warnings: pdWarnings, errors: pdErrors } = pex.validateDefinition(presentationDefinition);

const presentationSubmission = {
  ...
};

const { warnings: psWarnings, errors: psErrors } = pex.validateSubmission(presentationSubmission);

API

Evaluate

PEX.evaluatePresentation(presentationDefinition, verifiablePresentation);
PEXv1.evaluatePresentation(presentationDefinition, verifiablePresentation);
PEXv2.evaluatePresentation(presentationDefinition, verifiablePresentation);
Description

These three methods are quite similar. The first One receives a presentation definition object, decides the version based upon feature detection and act accordingly. The other two are specific to their version.

For more detailed difference between v1 and v2 please read the From V1 to V2 section.

Evaluates whether a presentation submission meets the requested presentation definition Since this method will be used both before and after creating a IVerifiablePresentation, we accept both signed and unsigned version of a presentation here.

Parameters

name type description
presentationDefinition PresentationDefinition the presentation definition that initiated the request from the verifier
presentation IPresentation the IPresentation object containing the required credentials and a presentation_submission object mapping back to the presentation definition

Return value

If evaluation is successful, value will be a non-null PresentationSubmission mapping the submitted credentials to the requested inputs.

interface EvaluationResults {
  value?: PresentationSubmission;
  warnings?: string[];
  errors?: Error[];
  verifiableCredential: IVerifiableCredential[];
}

SelectFrom

PEX.selectFrom(presentationDefinition, credentials, holderDids);
PEXv1.selectFrom(presentationDefinitionV1, credentials, holderDids);
PEXv2.selectFrom(presentationDefinitionV2, credentials, holderDids);
Description

These three methods are quite similar. The first One receives a presentation definition object, decides the version based upon feature detection and act accordingly. The other two are specific to their version.

For more detailed difference between v1 and v2 please read the From V1 to V2 section.

Gathers the matching credentials that fit a given presentation definition

selectFrom Parameters

name type description
presentationDefinition PresentationDefinition the presentation definition that initiated the request from the verifier
credentials IVerifiableCredential[] the array of verifiable credentials to select from
holderDids string[] the holder's dids. this can be found in IVerifiablePresentation's holder property note that a wallet can have many holderDids retrieved from different places

Return value

  • If the selection was successful or partially successful, the matches array will consist of SubmissionRequirementMatch object(s), representing the matching credentials for each SubmissionRequirement in the presentationDefinition input parameter.
  • If the selection was not successful, the errors array will consist of Checked object(s), representing what has failed in your selection process.
import { Status } from './ConstraintUtils';

interface SelectResults {
  errors?: Checked[];
  matches?: SubmissionRequirementMatch[];
  /**
   * This is the parameter that pejs library user should look into to determine what to do next
   * Status can have three values:
   *  1. INFO: everything is fine, you can call `presentationFrom` after this method
   *  2. WARN: method was called with more credentials than required.
   *       To enhance credential holder's privacy it is recommended to select credentials which are absolutely required.
   *  3. Error: the credentials you've sent didn't satisfy the requirement defined presentationDefinition object
   */
  areRequiredCredentialsPresent: Status;
  /**
   * All matched/selectable credentials
   */
  verifiableCredential?: IVerifiableCredential[];
  /**
   * Following are indexes of the verifiableCredentials passed to the selectFrom method that have been selected.
   */
  vcIndexes?: number[];
  warnings?: Checked[];
}

interface SubmissionRequirementMatch {
  name?: string;
  rule: Rules;
  min?: number;
  count?: number;
  max?: number;
  vc_path: string[];
  from?: string[];
  from_nested?: SubmissionRequirementMatch[]; // IVerifiableCredential Address
}

PresentationFrom

PEX.presentationFrom(presentationDefinition, selectedCredentials, holderDID);
PEXv1.presentationFrom(presentationDefinitionV1, selectedCredentials, holderDID);
PEXv2.presentationFrom(presentationDefinitionV2, selectedCredentials, holderDID);
Description

These three methods are quite similar. The first One receives a presentation definition object, decides the version based upon feature detection and act accordingly. The other two are specific to their version.

For more detailed difference between v1 and v2 please read the From V1 to V2 section.

Creates the corresponding IPresentation Submission object to be included in the Verifiable IPresentation response, which maps the submitted credentials to the requested inputs in the presentationDefinition input parameter.

presentationFromV1 Parameters

name type description
presentationDefinition PresentationDefinition the v1 presentation definition that initiated the request from the verifier
selectedCredentials IVerifiableCredential[] the array of verifiable credentials that meet the submission requirements in the presentation definition
holderDid string the holder's DID. This can be found in IVerifiablePresentation's holder property note that a wallet can have many holderDIDs retrieved from different places

presentationFromV2 Parameters

name type description
presentationDefinition PresentationDefinition the v2 presentation definition that initiated the request from the verifier
selectedCredentials IVerifiableCredential[] the array of verifiable credentials that meet the submission requirements in the presentation definition
holderDid string the holder's DID. This can be found in IVerifiablePresentation's holder property note that a wallet can have many holderDIDs retrieved from different places

Return value

If the selected credentials successfully match the submission requirements in the presentation definition, the return value will be a non-null PresentationSubmission

interface PresentationSubmission {
  id?: string;
  definition_id: string;
  descriptor_map: Descriptor[];
}

Validation

PEX.validateDefinition(objToValidate);
PEXv1.validateDefinition(objToValidate);
PEXv2.validateDefinition(objToValidate);
validateSubmission(objToValidate);

Description

A validation utility function for PresentationDefinition and PresentationSubmission objects. If you know the version of your presentation definition you can call version-specific functions. If not you can call the general one (located in PEX) to first determine the version and then validate the presentation definition object against that version's specific rules.

Parameters

name type description
objToValidate PresentationDefinition | PresentationSubmission the presentation definition or presentation submission to be validated

Return value

The validate method returns a validated results array NonEmptyArray<Checked> , with structure:

interface Checked {
  tag: string;
  status: Status;
  message?: string;
}

status can have following values 'info' | 'warn' | 'error'

Definition Version Discovery

PEX.definitionVersionDiscovery(presentationDefinition);

Description

A utility function for PresentationDefinition objects. This method will determine the version of your presentationDefinition object.

Parameters

name type description
presentationDefinition PresentationDefinition the presentation definition that you need to decide the version for

Return value

The definitionVersionDiscovery method returns a version or an error, with following structure:

{
  version? : PEVersion;
  error? : string;
}

enum PEVersion {
  v1 = 'v1',
  v2 = 'v2',
}

From V1 to V2

The following changes has been made in the v2 version of the IPresentation Exchange specification:

  1. schema has been removed from InputDescriptor properties.
  2. presentation_definition has another property called frame and if present, its value MUST be a JSON LD Framing Document object
  3. filter has several more options for filtering:
    • formatMaximum
    • formatMinimum
    • formatExclusiveMaximum
    • formatExclusiveMinimum

As a result we introduced the PEX class to replace the former PEJS class. This class does feature detection on the presentation definition to determine whether it is a v1 or v2 spec. Then it delegates the functionality to the PEXv1 or PEXv2 class respectively.

Workflow Diagram

Flow diagram

For PEX developers

This project has been created using:

  • yarn version 1.22.5
  • node version 12.22.1

Install

yarn install

Build

yarn build

Test

The test command runs:

  • eslint
  • prettier
  • unit

You can also run only a single section of these tests, using for example yarn test:unit.

yarn test

Utility scripts

There are several other utility scripts that help with development.

  • yarn fix - runs eslint --fix as well as prettier to fix code style
  • yarn cov - generates code coverage report

Glossary

Term Definition
ICredential A set of one or more claims made by an issuer.
Verifiable ICredential Is a tamper-evident credential that has authorship that can be cryptographically verified. Verifiable credentials can be used to build verifiable presentations, which can also be cryptographically verified. The claims in a credential can be about different subjects.
IPresentation Definition IPresentation Definitions are objects that articulate what proofs a Verifier requires.
Holder Holders are entities that have one or more verifiable credentials in their possession. Holders are also the entities that submit proofs to Verifiers to satisfy the requirements described in a IPresentation Definition.
Holder's Did Unique ID URI string and PKI metadata document format for describing the cryptographic keys and other fundamental PKI values linked to a unique, user-controlled, self-sovereign identifier in holder's wallet
Verifier Verifiers are entities that define what proofs they require from a Holder (via a IPresentation Definition) in order to proceed with an interaction.
IIssuer A role an entity can perform by asserting claims about one or more subjects, creating a verifiable credential from these claims, and transmitting the verifiable credential to a holder.
IPresentation Data derived from one or more verifiable credentials, issued by one or more issuers
Verifiable IPresentation Is a tamper-evident presentation encoded in such a way that authorship of the data can be trusted after a process of cryptographic verification.

Further work:

  1. We know of some potential issues with JWT based (de)serialization
  2. Support for hashlinks is not incorporated. We do not inspect them and just treat them as normal URIs
  3. In the DIF documentation some entries are addressing nested credentials and nested paths these are currently not fully support yet.