Source: utils/revocation.js

import { randomAsHex, blake2AsHex } from '@polkadot/util-crypto';
import jsonld from 'jsonld';

import { VB_ACCUMULATOR_22 } from '@docknetwork/crypto-wasm-ts';
import OneOfPolicy from './revocation/one-of-policy';

import {
  RevRegType,
  DockRevRegQualifier,
  expandedStatusProperty,
  credentialIDField,
  credentialTypeField,
} from './vc/constants';

// The revocation registry has id with the byte size `RevRegIdByteSize`
export const RevRegIdByteSize = 32;
// Each entry in revocation registry has byte size `RevEntryByteSize`
export const RevEntryByteSize = 32;

const LD_SEC_TERM = 'https://ld.dock.io/security#';

/**
 * Return `credentialStatus` according to W3C spec when the revocation status is checked on Dock
 * @param registryId - Revocation registry id
 * @returns {{id: string, type: string}}
 */
export function buildDockCredentialStatus(registryId) {
  return { id: `${DockRevRegQualifier}${registryId}`, type: RevRegType };
}

/**
 * Generate the revocation id of a credential usable by Dock. It hashes the credential id to get the
 * revocation id
 * @param credential
 * @returns {*}
 */
export function getDockRevIdFromCredential(credential) {
  // The hash outputs the same number of bytes as required by Dock
  return blake2AsHex(credential[credentialIDField], RevEntryByteSize * 8);
}

/**
 * Generate a random revocation registry id.
 * @returns {string} The id as a hex string
 */
export function createRandomRegistryId() {
  return randomAsHex(RevRegIdByteSize);
}

/**
 * Retrieves a value under the `credentialStatus` property and ensures it has the expected properties.
 * Returns `null` if no value is found.
 * @param expanded
 */
export function getCredentialStatus(expanded) {
  const statusValues = jsonld.getValues(expanded, expandedStatusProperty);
  if (!statusValues.length) {
    return null;
  } else if (statusValues.length > 1) {
    throw new Error(
      `\`statusPurpose\` must be an array containing up to one item, received: \`${expanded[expandedStatusProperty]}\``,
    );
  }
  const [status] = statusValues;

  if (!status[credentialIDField]) {
    throw new Error('"credentialStatus" must include an id.');
  }
  if (!status[credentialTypeField]) {
    throw new Error('"credentialStatus" must include a type.');
  }

  return status;
}

/**
 * Returns `true` if supplied status is a registry revocation status.
 * @param status
 * @returns {boolean}
 */
export const isRegistryRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(RevRegType) || type.includes(`${LD_SEC_TERM}${RevRegType}`) || type.includes(`/${RevRegType}`);

/**
 * Returns `true` if supplied status is a accumulator revocation status.
 * @param status
 * @returns {boolean}
 */
export const isAccumulatorRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(VB_ACCUMULATOR_22) || type.includes(`${LD_SEC_TERM}${VB_ACCUMULATOR_22}`) || type.includes(`/${VB_ACCUMULATOR_22}`);

/**
 * Checks if a credential status has a registry revocation.
 * @param expanded
 * @returns {boolean}
 */
export function hasRegistryRevocationStatus(expanded) {
  const status = getCredentialStatus(expanded);

  return !!status && isRegistryRevocationStatus(status);
}

/**
 * Check if the credential is revoked or not according to the revocation registry mechanism.
 * @param credential
 * @param documentLoader
 * @returns {Promise<{verified: boolean}|{verified: boolean, error: string}>} The returned object will have a key `verified`
 * which is true if the credential is not revoked and false otherwise. The `error` will describe the error if any.
 */
export async function checkRevocationRegistryStatus(
  credential,
  documentLoader,
) {
  const status = getCredentialStatus(credential);
  const revId = getDockRevIdFromCredential(credential);
  if (!status) {
    throw new Error('Missing `credentialStatus`');
  }

  if (!isRegistryRevocationStatus(status)) {
    throw new Error(`Expected registry revocation status, got \`${status}\``);
  }

  const regId = status[credentialIDField];
  const fullId = `${regId}#${revId}`;

  // Hash credential id to get revocation id
  const { document: revocationStatus } = await documentLoader(fullId);
  if (revocationStatus) {
    return { verified: false, error: 'Revocation check failed' };
  }

  return { verified: true };
}

export { OneOfPolicy, RevRegType, DockRevRegQualifier };