Source: presentation.js

import {
  BBSPlusPublicKeyG2,
  initializeWasm,
  isWasmInitialized,
  BBSPublicKey,
  PSPublicKey,
  PresentationBuilder, flattenObjectToKeyValuesList,
} from '@docknetwork/crypto-wasm-ts';
import b58 from 'bs58';
import { stringToU8a } from '@polkadot/util';
import { ensureArray } from './utils/type-helpers';

import Bls12381BBSSignatureDock2022 from './utils/vc/crypto/Bls12381BBSSignatureDock2022';
import { DOCK_ANON_CREDENTIAL_ID } from './utils/vc/crypto/common/DockCryptoSignatureProof';
import {
  Bls12381BBSSigDockSigName,
  Bls12381PSSigDockSigName,
  Bls12381BBS23SigDockSigName,
  Bls12381PSSigProofDockSigName,
  Bls12381BBS23SigProofDockSigName,
  Bls12381BBSSigProofDockSigName, Bls12381BDDT16MacDockName, Bls12381BDDT16MacProofDockName,
} from './utils/vc/crypto/constants';
import defaultDocumentLoader from './utils/vc/document-loader';
import {
  Bls12381BBSSignatureDock2023,
  Bls12381PSSignatureDock2023,
} from './utils/vc/custom_crypto';
import Bls12381BDDT16MACDock2024 from './utils/vc/crypto/Bls12381BDDT16MACDock2024';

const SIG_NAME_TO_PROOF_NAME = Object.setPrototypeOf(
  {
    [Bls12381BBSSigDockSigName]: Bls12381BBSSigProofDockSigName,
    [Bls12381BBS23SigDockSigName]: Bls12381BBS23SigProofDockSigName,
    [Bls12381PSSigDockSigName]: Bls12381PSSigProofDockSigName,
    [Bls12381BDDT16MacDockName]: Bls12381BDDT16MacProofDockName,
  },
  null,
);

export default class Presentation {
  /**
   * Create a new BbsPlusPresentation instance.
   * @constructor
   */
  constructor() {
    this.presBuilder = new PresentationBuilder();
  }

  /**
   * Species attributes to be revealed in the credential
   * @param {number} credentialIndex
   * @param {Array.<string>} attributes
   */
  addAttributeToReveal(credentialIndex, attributes = []) {
    ensureArray(attributes);
    this.presBuilder.markAttributesRevealed(
      credentialIndex,
      new Set(attributes),
    );
  }

  /**
   * Create a presentation
   * @param options
   * @returns {object}
   */
  createPresentation(options = {}) {
    const { nonce, context } = options;
    if (nonce) {
      this.presBuilder.nonce = stringToU8a(nonce);
    }
    if (context) {
      this.presBuilder.context = context;
    }
    const pres = this.presBuilder.finalize();
    return pres.toJSON();
  }

  /**
   * Adds a JSON-LD credential to be presented
   * @param credentialLD
   * @param options
   * @returns {Promise<number>}
   */
  async addCredentialToPresent(credentialLD, options = {}) {
    if (options.documentLoader && options.resolver) {
      throw new Error(
        'Passing resolver and documentLoader results in resolver being ignored, please re-factor.',
      );
    }
    if (!isWasmInitialized()) {
      await initializeWasm();
    }

    const documentLoader = options.documentLoader || defaultDocumentLoader(options.resolver);

    const json = typeof credentialLD === 'string'
      ? JSON.parse(credentialLD)
      : credentialLD;

    const { proof } = json;

    if (!proof) {
      throw new Error('Credential does not have a proof');
    }

    let Signature;
    let PublicKey;
    let isKvac = false;
    let pk;

    switch (proof.type) {
      case Bls12381BBSSigDockSigName:
        Signature = Bls12381BBSSignatureDock2022;
        PublicKey = BBSPlusPublicKeyG2;
        break;
      case Bls12381BBS23SigDockSigName:
        Signature = Bls12381BBSSignatureDock2023;
        PublicKey = BBSPublicKey;
        break;
      case Bls12381PSSigDockSigName:
        Signature = Bls12381PSSignatureDock2023;
        PublicKey = PSPublicKey;
        break;
      case Bls12381BDDT16MacDockName:
        Signature = Bls12381BDDT16MACDock2024;
        PublicKey = undefined;
        isKvac = true;
        break;
      default:
        throw new Error(`Invalid proof type ${proof.type}`);
    }

    if (!isKvac) {
      const keyDocument = await Signature.getVerificationMethod({
        proof,
        documentLoader,
      });

      const pkRaw = b58.decode(keyDocument.publicKeyBase58);
      pk = new PublicKey(pkRaw);
    }

    const convertedCredential = Signature.convertCredentialForPresBuilding({ document: json });
    const idx = this.presBuilder.addCredential(convertedCredential, pk);

    // Enforce revealing of verificationMethod and type
    // We also require context and type for JSON-LD
    const toReveal = [
      'proof.type',
      'proof.verificationMethod',
      '@context',
      'type',
    ];
    // If the issuer field is defined in the credential, reveal it.
    if (json.issuer !== undefined) {
      if (typeof json.issuer === 'string') {
        toReveal.push('issuer');
      } else if (Array.isArray(json.issuer)) {
        throw new Error(`"issuer" cant be an array but was ${json.issuer}`);
      } else if (typeof json.issuer === 'object') {
        const [names, _] = flattenObjectToKeyValuesList(json.issuer);
        toReveal.push(...names.map((n) => `issuer.${n}`));
      } else {
        throw new Error(`Unexpected value for "issuer" ${json.issuer}`);
      }
    }
    this.addAttributeToReveal(idx, toReveal);
    return idx;
  }

  deriveCredentials(options) {
    const presentation = this.createPresentation(options);
    const { credentials } = presentation.spec;
    if (credentials.length > 1) {
      throw new Error(
        'Cannot derive from multiple credentials in a presentation',
      );
    }

    return credentials.map((credential) => {
      if (!credential.revealedAttributes.proof) {
        throw new Error('Credential proof is not revealed, it should be');
      }

      const date = new Date().toISOString();
      const type = SIG_NAME_TO_PROOF_NAME[credential.revealedAttributes.proof.type];
      if (type == null) {
        throw new Error(
          `Failed to map credential signature type to the proof type: ${credential.revealedAttributes.proof.type}`,
        );
      }

      const w3cFormattedCredential = {
        ...credential.revealedAttributes,
        // ID required for W£C formatted credentials, if not revealed used a static URI
        id: credential.revealedAttributes.id || DOCK_ANON_CREDENTIAL_ID,
        '@context': JSON.parse(credential.revealedAttributes['@context']),
        type: JSON.parse(credential.revealedAttributes.type),
        credentialSchema: JSON.parse(credential.schema),
        issuer:
          credential.revealedAttributes.issuer
          || credential.revealedAttributes.proof.verificationMethod.split('#')[0],
        issuanceDate: credential.revealedAttributes.issuanceDate || date,
        proof: {
          proofPurpose: 'assertionMethod',
          created: date,
          ...credential.revealedAttributes.proof,
          type,
          // Question: Why each cred will have the same proof, nonce, context, etc. They don't apply here. And is the same proof repeatedly verified?
          proofValue: presentation.proof,
          nonce: presentation.nonce,
          context: presentation.context,
          attributeCiphertexts: presentation.attributeCiphertexts,
          attributeEqualities: presentation.spec.attributeEqualities,
          boundedPseudonyms: presentation.spec.boundedPseudonyms,
          unboundedPseudonyms: presentation.spec.unboundedPseudonyms,
          version: credential.version,
          // This is the presentation version and not the credential version. Continuing the stupidity by adding presentation version to the credential version so that it can be later retrieved
          presVersion: presentation.version,
          sigType: credential.sigType,
          bounds: credential.bounds,
        },
      };

      if (credential.status) {
        w3cFormattedCredential.credentialStatus = credential.status;
      }

      return w3cFormattedCredential;
    });
  }
}