Source: verifiable-presentation.js

import { signPresentation, verifyPresentation } from './utils/vc/index';

import {
  ensureObjectWithId,
  ensureString,
  ensureURI,
  isObject,
} from './utils/type-helpers';

import { getUniqueElementsFromArray } from './utils/misc';
import VerifiableCredential from './verifiable-credential';
import DIDResolver from "./resolver/did/did-resolver"; // eslint-disable-line

const DEFAULT_CONTEXT = 'https://www.w3.org/2018/credentials/v1';
const DEFAULT_TYPE = 'VerifiablePresentation';

/**
 * @typedef {object} VerifiablePresentationVerificationResult The presentation verification result
 * @property {object} presentationResult Is this presentqtion verified or not
 * @property {array} credentialResults Verification results
 * @property {Boolean} verified Is verified or not
 * @property {any} [error] Optional error
 */

/**
 * Representation of a Verifiable Presentation.
 */
class VerifiablePresentation {
  /**
   * Create a new Verifiable Presentation instance.
   * @param {string} id - id of the presentation
   * @constructor
   */
  constructor(id) {
    ensureURI(id);
    this.id = id;
    this.context = [DEFAULT_CONTEXT];
    this.type = [DEFAULT_TYPE];
    this.credentials = [];
    this.proof = null;
  }

  static fromJSON(json) {
    const {
      verifiableCredential, id, type, ...rest
    } = json;
    const vp = new VerifiablePresentation(id);

    if (type) {
      vp.type = [];
      if (type.length !== undefined) {
        type.forEach((typeVal) => {
          vp.addType(typeVal);
        });
      } else {
        vp.addType(type);
      }
    } else {
      throw new Error(
        'No type found in JSON object, verifiable presentations must have a type field.',
      );
    }

    const context = rest['@context'];
    if (context) {
      vp.setContext(rest['@context']);
      delete rest['@context'];
    } else {
      throw new Error(
        'No context found in JSON object, verifiable presentations must have a @context field.',
      );
    }

    if (verifiableCredential) {
      if (verifiableCredential.length) {
        verifiableCredential.forEach((credential) => {
          vp.addCredential(credential);
        });
      } else {
        vp.addCredential(verifiableCredential);
      }
    }

    Object.assign(vp, rest);
    return vp;
  }

  /**
   * Sets the context to the given value, overrding all others
   * @param {string|object} context - Context to assign
   * @returns {VerifiablePresentation}
   */
  setContext(context) {
    if (!isObject(context) && !Array.isArray(context)) {
      ensureURI(context);
    }
    this.context = context;
    return this;
  }

  /**
   * Add a context to this Presentation's context array. Duplicates are omitted.
   * @param {string|object} context - Context to add to the presentation context array
   * @returns {VerifiablePresentation}
   */
  addContext(context) {
    if (!isObject(context)) {
      ensureURI(context);
    }
    this.context = getUniqueElementsFromArray(
      [...this.context, context],
      JSON.stringify,
    );
    return this;
  }

  /**
   * Add a type to this Presentation's type array. Duplicates are omitted.
   * @param {string} type - Type to add to the presentation type array
   * @returns {VerifiablePresentation}
   */
  addType(type) {
    ensureString(type);
    this.type = [...new Set([...this.type, type])];
    return this;
  }

  /**
   * Set a holder for this Presentation
   * @param {string} holder - Holder to add to the presentation
   * @returns {VerifiablePresentation}
   */
  setHolder(holder) {
    ensureURI(holder);
    this.holder = holder;
    return this;
  }

  /**
   * Add a Verifiable Credential to this Presentation. Duplicates will be ignored.
   * @param {object} credential -  Verifiable Credential for the presentation
   * @returns {VerifiablePresentation}
   */
  addCredential(credential) {
    let cred = credential;
    if (credential instanceof VerifiableCredential) {
      cred = credential.toJSON();
    }
    ensureObjectWithId(cred, 'credential');
    this.credentials = getUniqueElementsFromArray(
      [...this.credentials, cred],
      JSON.stringify,
    );

    return this;
  }

  /**
   * Add multiple Verifiable Credentials to this Presentation. Duplicates will be ignored.
   * @param {Array<object>} credentials -  Verifiable Credential for the presentation
   * @returns {VerifiablePresentation}
   */
  addCredentials(credentials) {
    credentials.forEach(this.addCredential.bind(this));
  }

  /**
   * Define the JSON representation of a Verifiable Presentation.
   * @returns {object}
   */
  toJSON() {
    const { context, credentials, ...rest } = this;
    return {
      '@context': context,
      verifiableCredential: credentials,
      ...rest,
    };
  }

  /**
   * Sign a Verifiable Presentation using the provided keyDoc
   * @param {object} keyDoc - document with `id`, `controller`, `type`, `privateKeyBase58` and `publicKeyBase58`
   * @param {string} challenge - proof challenge Required.
   * @param {string} domain - proof domain (optional)
   * @param {DIDResolver} [resolver] - Resolver for DIDs.
   * @param {Boolean} [compactProof] - Whether to compact the JSON-LD or not.
   * @returns {Promise<VerifiablePresentation>}
   */
  async sign(keyDoc, challenge, domain, resolver = null, compactProof = true) {
    const signedVP = await signPresentation(
      this.toJSON(),
      keyDoc,
      challenge,
      domain,
      resolver,
      compactProof,
    );
    this.context = signedVP['@context'];
    this.proof = signedVP.proof;
    return this;
  }

  /**
   * Verify a Verifiable Presentation
   * @param {object} [params] Verify parameters (TODO: add type info for this object)
   * @returns {Promise<VerifiablePresentationVerificationResult>} - verification result.
   */
  async verify({
    challenge,
    domain,
    resolver = null,
    compactProof = true,
    skipRevocationCheck = false,
    skipSchemaCheck = false,
    verifyMatchingIssuersForRevocation = true,
    suite = [],
  } = {}) {
    if (!this.proof) {
      throw new Error('The current VerifiablePresentation has no proof.');
    }

    return verifyPresentation(this.toJSON(), {
      challenge,
      domain,
      resolver,
      compactProof,
      skipRevocationCheck,
      skipSchemaCheck,
      verifyMatchingIssuersForRevocation,
      suite,
    });
  }
}

export default VerifiablePresentation;