// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

// @ts-check
/// <reference path="../../core/internal.d.ts" />
/// <reference path="../../core/lib.deno_core.d.ts" />
/// <reference path="../webidl/internal.d.ts" />
/// <reference path="../web/lib.deno_web.d.ts" />

const core = globalThis.Deno.core;
const ops = core.ops;
const primordials = globalThis.__bootstrap.primordials;
import * as webidl from "internal:deno_webidl/00_webidl.js";
import DOMException from "internal:deno_web/01_dom_exception.js";
const {
  ArrayBufferPrototype,
  ArrayBufferIsView,
  ArrayPrototypeEvery,
  ArrayPrototypeFind,
  ArrayPrototypeIncludes,
  BigInt64ArrayPrototype,
  BigUint64ArrayPrototype,
  Int16ArrayPrototype,
  Int32ArrayPrototype,
  Int8ArrayPrototype,
  JSONParse,
  JSONStringify,
  MathCeil,
  ObjectAssign,
  ObjectPrototypeHasOwnProperty,
  ObjectPrototypeIsPrototypeOf,
  StringPrototypeToLowerCase,
  StringPrototypeToUpperCase,
  StringPrototypeCharCodeAt,
  StringFromCharCode,
  SafeArrayIterator,
  Symbol,
  SymbolFor,
  SyntaxError,
  TypedArrayPrototypeSlice,
  TypeError,
  Uint16ArrayPrototype,
  Uint32ArrayPrototype,
  Uint8Array,
  Uint8ArrayPrototype,
  Uint8ClampedArrayPrototype,
  WeakMap,
  WeakMapPrototypeGet,
  WeakMapPrototypeSet,
} = primordials;

// P-521 is not yet supported.
const supportedNamedCurves = ["P-256", "P-384"];
const recognisedUsages = [
  "encrypt",
  "decrypt",
  "sign",
  "verify",
  "deriveKey",
  "deriveBits",
  "wrapKey",
  "unwrapKey",
];

const simpleAlgorithmDictionaries = {
  AesGcmParams: { iv: "BufferSource", additionalData: "BufferSource" },
  RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" },
  EcKeyGenParams: {},
  HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" },
  RsaPssParams: {},
  EcdsaParams: { hash: "HashAlgorithmIdentifier" },
  HmacImportParams: { hash: "HashAlgorithmIdentifier" },
  HkdfParams: {
    hash: "HashAlgorithmIdentifier",
    salt: "BufferSource",
    info: "BufferSource",
  },
  Pbkdf2Params: { hash: "HashAlgorithmIdentifier", salt: "BufferSource" },
  RsaOaepParams: { label: "BufferSource" },
  RsaHashedImportParams: { hash: "HashAlgorithmIdentifier" },
  EcKeyImportParams: {},
};

const supportedAlgorithms = {
  "digest": {
    "SHA-1": null,
    "SHA-256": null,
    "SHA-384": null,
    "SHA-512": null,
  },
  "generateKey": {
    "RSASSA-PKCS1-v1_5": "RsaHashedKeyGenParams",
    "RSA-PSS": "RsaHashedKeyGenParams",
    "RSA-OAEP": "RsaHashedKeyGenParams",
    "ECDSA": "EcKeyGenParams",
    "ECDH": "EcKeyGenParams",
    "AES-CTR": "AesKeyGenParams",
    "AES-CBC": "AesKeyGenParams",
    "AES-GCM": "AesKeyGenParams",
    "AES-KW": "AesKeyGenParams",
    "HMAC": "HmacKeyGenParams",
    "X25519": null,
    "Ed25519": null,
  },
  "sign": {
    "RSASSA-PKCS1-v1_5": null,
    "RSA-PSS": "RsaPssParams",
    "ECDSA": "EcdsaParams",
    "HMAC": null,
    "Ed25519": null,
  },
  "verify": {
    "RSASSA-PKCS1-v1_5": null,
    "RSA-PSS": "RsaPssParams",
    "ECDSA": "EcdsaParams",
    "HMAC": null,
    "Ed25519": null,
  },
  "importKey": {
    "RSASSA-PKCS1-v1_5": "RsaHashedImportParams",
    "RSA-PSS": "RsaHashedImportParams",
    "RSA-OAEP": "RsaHashedImportParams",
    "ECDSA": "EcKeyImportParams",
    "ECDH": "EcKeyImportParams",
    "HMAC": "HmacImportParams",
    "HKDF": null,
    "PBKDF2": null,
    "AES-CTR": null,
    "AES-CBC": null,
    "AES-GCM": null,
    "AES-KW": null,
    "Ed25519": null,
    "X25519": null,
  },
  "deriveBits": {
    "HKDF": "HkdfParams",
    "PBKDF2": "Pbkdf2Params",
    "ECDH": "EcdhKeyDeriveParams",
    "X25519": "EcdhKeyDeriveParams",
  },
  "encrypt": {
    "RSA-OAEP": "RsaOaepParams",
    "AES-CBC": "AesCbcParams",
    "AES-GCM": "AesGcmParams",
    "AES-CTR": "AesCtrParams",
  },
  "decrypt": {
    "RSA-OAEP": "RsaOaepParams",
    "AES-CBC": "AesCbcParams",
    "AES-GCM": "AesGcmParams",
    "AES-CTR": "AesCtrParams",
  },
  "get key length": {
    "AES-CBC": "AesDerivedKeyParams",
    "AES-CTR": "AesDerivedKeyParams",
    "AES-GCM": "AesDerivedKeyParams",
    "AES-KW": "AesDerivedKeyParams",
    "HMAC": "HmacImportParams",
    "HKDF": null,
    "PBKDF2": null,
  },
  "wrapKey": {
    "AES-KW": null,
  },
  "unwrapKey": {
    "AES-KW": null,
  },
};

const aesJwkAlg = {
  "AES-CTR": {
    128: "A128CTR",
    192: "A192CTR",
    256: "A256CTR",
  },
  "AES-CBC": {
    128: "A128CBC",
    192: "A192CBC",
    256: "A256CBC",
  },
  "AES-GCM": {
    128: "A128GCM",
    192: "A192GCM",
    256: "A256GCM",
  },
  "AES-KW": {
    128: "A128KW",
    192: "A192KW",
    256: "A256KW",
  },
};

// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm
// 18.4.4
function normalizeAlgorithm(algorithm, op) {
  if (typeof algorithm == "string") {
    return normalizeAlgorithm({ name: algorithm }, op);
  }

  // 1.
  const registeredAlgorithms = supportedAlgorithms[op];
  // 2. 3.
  const initialAlg = webidl.converters.Algorithm(algorithm, {
    prefix: "Failed to normalize algorithm",
    context: "passed algorithm",
  });
  // 4.
  let algName = initialAlg.name;

  // 5.
  let desiredType = undefined;
  for (const key in registeredAlgorithms) {
    if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) {
      continue;
    }
    if (
      StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName)
    ) {
      algName = key;
      desiredType = registeredAlgorithms[key];
    }
  }
  if (desiredType === undefined) {
    throw new DOMException(
      "Unrecognized algorithm name",
      "NotSupportedError",
    );
  }

  // Fast path everything below if the registered dictionary is "None".
  if (desiredType === null) {
    return { name: algName };
  }

  // 6.
  const normalizedAlgorithm = webidl.converters[desiredType](algorithm, {
    prefix: "Failed to normalize algorithm",
    context: "passed algorithm",
  });
  // 7.
  normalizedAlgorithm.name = algName;

  // 9.
  const dict = simpleAlgorithmDictionaries[desiredType];
  // 10.
  for (const member in dict) {
    if (!ObjectPrototypeHasOwnProperty(dict, member)) {
      continue;
    }
    const idlType = dict[member];
    const idlValue = normalizedAlgorithm[member];
    // 3.
    if (idlType === "BufferSource" && idlValue) {
      normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
        new Uint8Array(
          ArrayBufferIsView(idlValue) ? idlValue.buffer : idlValue,
          idlValue.byteOffset ?? 0,
          idlValue.byteLength,
        ),
      );
    } else if (idlType === "HashAlgorithmIdentifier") {
      normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, "digest");
    } else if (idlType === "AlgorithmIdentifier") {
      // TODO(lucacasonato): implement
      throw new TypeError("unimplemented");
    }
  }

  return normalizedAlgorithm;
}

/**
 * @param {ArrayBufferView | ArrayBuffer} input
 * @returns {Uint8Array}
 */
function copyBuffer(input) {
  return TypedArrayPrototypeSlice(
    ArrayBufferIsView(input)
      ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength)
      : new Uint8Array(input),
  );
}

const _handle = Symbol("[[handle]]");
const _algorithm = Symbol("[[algorithm]]");
const _extractable = Symbol("[[extractable]]");
const _usages = Symbol("[[usages]]");
const _type = Symbol("[[type]]");

class CryptoKey {
  /** @type {string} */
  [_type];
  /** @type {boolean} */
  [_extractable];
  /** @type {object} */
  [_algorithm];
  /** @type {string[]} */
  [_usages];
  /** @type {object} */
  [_handle];

  constructor() {
    webidl.illegalConstructor();
  }

  /** @returns {string} */
  get type() {
    webidl.assertBranded(this, CryptoKeyPrototype);
    return this[_type];
  }

  /** @returns {boolean} */
  get extractable() {
    webidl.assertBranded(this, CryptoKeyPrototype);
    return this[_extractable];
  }

  /** @returns {string[]} */
  get usages() {
    webidl.assertBranded(this, CryptoKeyPrototype);
    // TODO(lucacasonato): return a SameObject copy
    return this[_usages];
  }

  /** @returns {object} */
  get algorithm() {
    webidl.assertBranded(this, CryptoKeyPrototype);
    // TODO(lucacasonato): return a SameObject copy
    return this[_algorithm];
  }

  [SymbolFor("Deno.customInspect")](inspect) {
    return `${this.constructor.name} ${
      inspect({
        type: this.type,
        extractable: this.extractable,
        algorithm: this.algorithm,
        usages: this.usages,
      })
    }`;
  }
}

webidl.configurePrototype(CryptoKey);
const CryptoKeyPrototype = CryptoKey.prototype;

/**
 * @param {string} type
 * @param {boolean} extractable
 * @param {string[]} usages
 * @param {object} algorithm
 * @param {object} handle
 * @returns
 */
function constructKey(type, extractable, usages, algorithm, handle) {
  const key = webidl.createBranded(CryptoKey);
  key[_type] = type;
  key[_extractable] = extractable;
  key[_usages] = usages;
  key[_algorithm] = algorithm;
  key[_handle] = handle;
  return key;
}

// https://w3c.github.io/webcrypto/#concept-usage-intersection
/**
 * @param {string[]} a
 * @param {string[]} b
 * @returns
 */
function usageIntersection(a, b) {
  return a.filter((i) => b.includes(i));
}

// TODO(lucacasonato): this should be moved to rust
/** @type {WeakMap<object, object>} */
const KEY_STORE = new WeakMap();

function getKeyLength(algorithm) {
  switch (algorithm.name) {
    case "AES-CBC":
    case "AES-CTR":
    case "AES-GCM":
    case "AES-KW": {
      // 1.
      if (!ArrayPrototypeIncludes([128, 192, 256], algorithm.length)) {
        throw new DOMException(
          "length must be 128, 192, or 256",
          "OperationError",
        );
      }

      // 2.
      return algorithm.length;
    }
    case "HMAC": {
      // 1.
      let length;
      if (algorithm.length === undefined) {
        switch (algorithm.hash.name) {
          case "SHA-1":
            length = 512;
            break;
          case "SHA-256":
            length = 512;
            break;
          case "SHA-384":
            length = 1024;
            break;
          case "SHA-512":
            length = 1024;
            break;
          default:
            throw new DOMException(
              "Unrecognized hash algorithm",
              "NotSupportedError",
            );
        }
      } else if (algorithm.length !== 0) {
        length = algorithm.length;
      } else {
        throw new TypeError("Invalid length.");
      }

      // 2.
      return length;
    }
    case "HKDF": {
      // 1.
      return null;
    }
    case "PBKDF2": {
      // 1.
      return null;
    }
    default:
      throw new TypeError("unreachable");
  }
}

class SubtleCrypto {
  constructor() {
    webidl.illegalConstructor();
  }

  /**
   * @param {string} algorithm
   * @param {BufferSource} data
   * @returns {Promise<Uint8Array>}
   */
  async digest(algorithm, data) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'digest' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 2, { prefix });
    algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
      prefix,
      context: "Argument 1",
    });
    data = webidl.converters.BufferSource(data, {
      prefix,
      context: "Argument 2",
    });

    data = copyBuffer(data);

    algorithm = normalizeAlgorithm(algorithm, "digest");

    const result = await core.opAsync(
      "op_crypto_subtle_digest",
      algorithm.name,
      data,
    );

    return result.buffer;
  }

  /**
   * @param {string} algorithm
   * @param {CryptoKey} key
   * @param {BufferSource} data
   * @returns {Promise<any>}
   */
  async encrypt(algorithm, key, data) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'encrypt' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 3, { prefix });
    algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
      prefix,
      context: "Argument 1",
    });
    key = webidl.converters.CryptoKey(key, {
      prefix,
      context: "Argument 2",
    });
    data = webidl.converters.BufferSource(data, {
      prefix,
      context: "Argument 3",
    });

    // 2.
    data = copyBuffer(data);

    // 3.
    const normalizedAlgorithm = normalizeAlgorithm(algorithm, "encrypt");

    // 8.
    if (normalizedAlgorithm.name !== key[_algorithm].name) {
      throw new DOMException(
        "Encryption algorithm doesn't match key algorithm.",
        "InvalidAccessError",
      );
    }

    // 9.
    if (!ArrayPrototypeIncludes(key[_usages], "encrypt")) {
      throw new DOMException(
        "Key does not support the 'encrypt' operation.",
        "InvalidAccessError",
      );
    }

    return await encrypt(normalizedAlgorithm, key, data);
  }

  /**
   * @param {string} algorithm
   * @param {CryptoKey} key
   * @param {BufferSource} data
   * @returns {Promise<any>}
   */
  async decrypt(algorithm, key, data) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'decrypt' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 3, { prefix });
    algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
      prefix,
      context: "Argument 1",
    });
    key = webidl.converters.CryptoKey(key, {
      prefix,
      context: "Argument 2",
    });
    data = webidl.converters.BufferSource(data, {
      prefix,
      context: "Argument 3",
    });

    // 2.
    data = copyBuffer(data);

    // 3.
    const normalizedAlgorithm = normalizeAlgorithm(algorithm, "decrypt");

    // 8.
    if (normalizedAlgorithm.name !== key[_algorithm].name) {
      throw new DOMException(
        "Decryption algorithm doesn't match key algorithm.",
        "OperationError",
      );
    }

    // 9.
    if (!ArrayPrototypeIncludes(key[_usages], "decrypt")) {
      throw new DOMException(
        "Key does not support the 'decrypt' operation.",
        "InvalidAccessError",
      );
    }

    const handle = key[_handle];
    const keyData = WeakMapPrototypeGet(KEY_STORE, handle);

    switch (normalizedAlgorithm.name) {
      case "RSA-OAEP": {
        // 1.
        if (key[_type] !== "private") {
          throw new DOMException(
            "Key type not supported",
            "InvalidAccessError",
          );
        }

        // 2.
        if (normalizedAlgorithm.label) {
          normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label);
        } else {
          normalizedAlgorithm.label = new Uint8Array();
        }

        // 3-5.
        const hashAlgorithm = key[_algorithm].hash.name;
        const plainText = await core.opAsync("op_crypto_decrypt", {
          key: keyData,
          algorithm: "RSA-OAEP",
          hash: hashAlgorithm,
          label: normalizedAlgorithm.label,
        }, data);

        // 6.
        return plainText.buffer;
      }
      case "AES-CBC": {
        normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);

        // 1.
        if (normalizedAlgorithm.iv.byteLength !== 16) {
          throw new DOMException(
            "Counter must be 16 bytes",
            "OperationError",
          );
        }

        const plainText = await core.opAsync("op_crypto_decrypt", {
          key: keyData,
          algorithm: "AES-CBC",
          iv: normalizedAlgorithm.iv,
          length: key[_algorithm].length,
        }, data);

        // 6.
        return plainText.buffer;
      }
      case "AES-CTR": {
        normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter);

        // 1.
        if (normalizedAlgorithm.counter.byteLength !== 16) {
          throw new DOMException(
            "Counter vector must be 16 bytes",
            "OperationError",
          );
        }

        // 2.
        if (
          normalizedAlgorithm.length === 0 || normalizedAlgorithm.length > 128
        ) {
          throw new DOMException(
            "Counter length must not be 0 or greater than 128",
            "OperationError",
          );
        }

        // 3.
        const cipherText = await core.opAsync("op_crypto_decrypt", {
          key: keyData,
          algorithm: "AES-CTR",
          keyLength: key[_algorithm].length,
          counter: normalizedAlgorithm.counter,
          ctrLength: normalizedAlgorithm.length,
        }, data);

        // 4.
        return cipherText.buffer;
      }
      case "AES-GCM": {
        normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);

        // 1.
        if (normalizedAlgorithm.tagLength === undefined) {
          normalizedAlgorithm.tagLength = 128;
        } else if (
          !ArrayPrototypeIncludes(
            [32, 64, 96, 104, 112, 120, 128],
            normalizedAlgorithm.tagLength,
          )
        ) {
          throw new DOMException(
            "Invalid tag length",
            "OperationError",
          );
        }

        // 2.
        if (data.byteLength < normalizedAlgorithm.tagLength / 8) {
          throw new DOMException(
            "Tag length overflows ciphertext",
            "OperationError",
          );
        }

        // 3. We only support 96-bit and 128-bit nonce.
        if (
          ArrayPrototypeIncludes(
            [12, 16],
            normalizedAlgorithm.iv.byteLength,
          ) === undefined
        ) {
          throw new DOMException(
            "Initialization vector length not supported",
            "NotSupportedError",
          );
        }

        // 4.
        if (normalizedAlgorithm.additionalData !== undefined) {
          if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) {
            throw new DOMException(
              "Additional data too large",
              "OperationError",
            );
          }
          normalizedAlgorithm.additionalData = copyBuffer(
            normalizedAlgorithm.additionalData,
          );
        }

        // 5-8.
        const plaintext = await core.opAsync("op_crypto_decrypt", {
          key: keyData,
          algorithm: "AES-GCM",
          length: key[_algorithm].length,
          iv: normalizedAlgorithm.iv,
          additionalData: normalizedAlgorithm.additionalData ||
            null,
          tagLength: normalizedAlgorithm.tagLength,
        }, data);

        // 9.
        return plaintext.buffer;
      }
      default:
        throw new DOMException("Not implemented", "NotSupportedError");
    }
  }

  /**
   * @param {string} algorithm
   * @param {CryptoKey} key
   * @param {BufferSource} data
   * @returns {Promise<any>}
   */
  async sign(algorithm, key, data) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'sign' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 3, { prefix });
    algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
      prefix,
      context: "Argument 1",
    });
    key = webidl.converters.CryptoKey(key, {
      prefix,
      context: "Argument 2",
    });
    data = webidl.converters.BufferSource(data, {
      prefix,
      context: "Argument 3",
    });

    // 1.
    data = copyBuffer(data);

    // 2.
    const normalizedAlgorithm = normalizeAlgorithm(algorithm, "sign");

    const handle = key[_handle];
    const keyData = WeakMapPrototypeGet(KEY_STORE, handle);

    // 8.
    if (normalizedAlgorithm.name !== key[_algorithm].name) {
      throw new DOMException(
        "Signing algorithm doesn't match key algorithm.",
        "InvalidAccessError",
      );
    }

    // 9.
    if (!ArrayPrototypeIncludes(key[_usages], "sign")) {
      throw new DOMException(
        "Key does not support the 'sign' operation.",
        "InvalidAccessError",
      );
    }

    switch (normalizedAlgorithm.name) {
      case "RSASSA-PKCS1-v1_5": {
        // 1.
        if (key[_type] !== "private") {
          throw new DOMException(
            "Key type not supported",
            "InvalidAccessError",
          );
        }

        // 2.
        const hashAlgorithm = key[_algorithm].hash.name;
        const signature = await core.opAsync("op_crypto_sign_key", {
          key: keyData,
          algorithm: "RSASSA-PKCS1-v1_5",
          hash: hashAlgorithm,
        }, data);

        return signature.buffer;
      }
      case "RSA-PSS": {
        // 1.
        if (key[_type] !== "private") {
          throw new DOMException(
            "Key type not supported",
            "InvalidAccessError",
          );
        }

        // 2.
        const hashAlgorithm = key[_algorithm].hash.name;
        const signature = await core.opAsync("op_crypto_sign_key", {
          key: keyData,
          algorithm: "RSA-PSS",
          hash: hashAlgorithm,
          saltLength: normalizedAlgorithm.saltLength,
        }, data);

        return signature.buffer;
      }
      case "ECDSA": {
        // 1.
        if (key[_type] !== "private") {
          throw new DOMException(
            "Key type not supported",
            "InvalidAccessError",
          );
        }

        // 2.
        const hashAlgorithm = normalizedAlgorithm.hash.name;
        const namedCurve = key[_algorithm].namedCurve;
        if (!ArrayPrototypeIncludes(supportedNamedCurves, namedCurve)) {
          throw new DOMException("Curve not supported", "NotSupportedError");
        }

        const signature = await core.opAsync("op_crypto_sign_key", {
          key: keyData,
          algorithm: "ECDSA",
          hash: hashAlgorithm,
          namedCurve,
        }, data);

        return signature.buffer;
      }
      case "HMAC": {
        const hashAlgorithm = key[_algorithm].hash.name;

        const signature = await core.opAsync("op_crypto_sign_key", {
          key: keyData,
          algorithm: "HMAC",
          hash: hashAlgorithm,
        }, data);

        return signature.buffer;
      }
      case "Ed25519": {
        // 1.
        if (key[_type] !== "private") {
          throw new DOMException(
            "Key type not supported",
            "InvalidAccessError",
          );
        }

        // https://briansmith.org/rustdoc/src/ring/ec/curve25519/ed25519/signing.rs.html#260
        const SIGNATURE_LEN = 32 * 2; // ELEM_LEN + SCALAR_LEN
        const signature = new Uint8Array(SIGNATURE_LEN);
        if (!ops.op_sign_ed25519(keyData, data, signature)) {
          throw new DOMException(
            "Failed to sign",
            "OperationError",
          );
        }
        return signature.buffer;
      }
    }

    throw new TypeError("unreachable");
  }

  /**
   * @param {string} format
   * @param {BufferSource} keyData
   * @param {string} algorithm
   * @param {boolean} extractable
   * @param {KeyUsages[]} keyUsages
   * @returns {Promise<any>}
   */
  // deno-lint-ignore require-await
  async importKey(format, keyData, algorithm, extractable, keyUsages) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'importKey' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 4, { prefix });
    format = webidl.converters.KeyFormat(format, {
      prefix,
      context: "Argument 1",
    });
    keyData = webidl.converters["BufferSource or JsonWebKey"](keyData, {
      prefix,
      context: "Argument 2",
    });
    algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
      prefix,
      context: "Argument 3",
    });
    extractable = webidl.converters.boolean(extractable, {
      prefix,
      context: "Argument 4",
    });
    keyUsages = webidl.converters["sequence<KeyUsage>"](keyUsages, {
      prefix,
      context: "Argument 5",
    });

    // 2.
    if (format !== "jwk") {
      if (
        ArrayBufferIsView(keyData) ||
        ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData)
      ) {
        keyData = copyBuffer(keyData);
      } else {
        throw new TypeError("keyData is a JsonWebKey");
      }
    } else {
      if (
        ArrayBufferIsView(keyData) ||
        ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData)
      ) {
        throw new TypeError("keyData is not a JsonWebKey");
      }
    }

    const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey");

    const algorithmName = normalizedAlgorithm.name;

    switch (algorithmName) {
      case "HMAC": {
        return importKeyHMAC(
          format,
          normalizedAlgorithm,
          keyData,
          extractable,
          keyUsages,
        );
      }
      case "ECDH":
      case "ECDSA": {
        return importKeyEC(
          format,
          normalizedAlgorithm,
          keyData,
          extractable,
          keyUsages,
        );
      }
      case "RSASSA-PKCS1-v1_5":
      case "RSA-PSS":
      case "RSA-OAEP": {
        return importKeyRSA(
          format,
          normalizedAlgorithm,
          keyData,
          extractable,
          keyUsages,
        );
      }
      case "HKDF": {
        return importKeyHKDF(format, keyData, extractable, keyUsages);
      }
      case "PBKDF2": {
        return importKeyPBKDF2(format, keyData, extractable, keyUsages);
      }
      case "AES-CTR":
      case "AES-CBC":
      case "AES-GCM": {
        return importKeyAES(
          format,
          normalizedAlgorithm,
          keyData,
          extractable,
          keyUsages,
          ["encrypt", "decrypt", "wrapKey", "unwrapKey"],
        );
      }
      case "AES-KW": {
        return importKeyAES(
          format,
          normalizedAlgorithm,
          keyData,
          extractable,
          keyUsages,
          ["wrapKey", "unwrapKey"],
        );
      }
      case "X25519": {
        return importKeyX25519(
          format,
          keyData,
          extractable,
          keyUsages,
        );
      }
      case "Ed25519": {
        return importKeyEd25519(
          format,
          keyData,
          extractable,
          keyUsages,
        );
      }
      default:
        throw new DOMException("Not implemented", "NotSupportedError");
    }
  }

  /**
   * @param {string} format
   * @param {CryptoKey} key
   * @returns {Promise<any>}
   */
  // deno-lint-ignore require-await
  async exportKey(format, key) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 2, { prefix });
    format = webidl.converters.KeyFormat(format, {
      prefix,
      context: "Argument 1",
    });
    key = webidl.converters.CryptoKey(key, {
      prefix,
      context: "Argument 2",
    });

    const handle = key[_handle];
    // 2.
    const innerKey = WeakMapPrototypeGet(KEY_STORE, handle);

    const algorithmName = key[_algorithm].name;

    let result;

    switch (algorithmName) {
      case "HMAC": {
        result = exportKeyHMAC(format, key, innerKey);
        break;
      }
      case "RSASSA-PKCS1-v1_5":
      case "RSA-PSS":
      case "RSA-OAEP": {
        result = exportKeyRSA(format, key, innerKey);
        break;
      }
      case "ECDH":
      case "ECDSA": {
        result = exportKeyEC(format, key, innerKey);
        break;
      }
      case "Ed25519": {
        result = exportKeyEd25519(format, key, innerKey);
        break;
      }
      case "X25519": {
        result = exportKeyX25519(format, key, innerKey);
        break;
      }
      case "AES-CTR":
      case "AES-CBC":
      case "AES-GCM":
      case "AES-KW": {
        result = exportKeyAES(format, key, innerKey);
        break;
      }
      default:
        throw new DOMException("Not implemented", "NotSupportedError");
    }

    if (key.extractable === false) {
      throw new DOMException(
        "Key is not extractable",
        "InvalidAccessError",
      );
    }

    return result;
  }

  /**
   * @param {AlgorithmIdentifier} algorithm
   * @param {CryptoKey} baseKey
   * @param {number | null} length
   * @returns {Promise<ArrayBuffer>}
   */
  async deriveBits(algorithm, baseKey, length) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'deriveBits' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 3, { prefix });
    algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
      prefix,
      context: "Argument 1",
    });
    baseKey = webidl.converters.CryptoKey(baseKey, {
      prefix,
      context: "Argument 2",
    });
    if (length !== null) {
      length = webidl.converters["unsigned long"](length, {
        prefix,
        context: "Argument 3",
      });
    }

    // 2.
    const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits");
    // 4-6.
    const result = await deriveBits(normalizedAlgorithm, baseKey, length);
    // 7.
    if (normalizedAlgorithm.name !== baseKey[_algorithm].name) {
      throw new DOMException("Invalid algorithm name", "InvalidAccessError");
    }
    // 8.
    if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveBits")) {
      throw new DOMException(
        "baseKey usages does not contain `deriveBits`",
        "InvalidAccessError",
      );
    }
    // 9-10.
    return result;
  }

  /**
   * @param {AlgorithmIdentifier} algorithm
   * @param {CryptoKey} baseKey
   * @param {number} length
   * @returns {Promise<ArrayBuffer>}
   */
  async deriveKey(
    algorithm,
    baseKey,
    derivedKeyType,
    extractable,
    keyUsages,
  ) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'deriveKey' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 5, { prefix });
    algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
      prefix,
      context: "Argument 1",
    });
    baseKey = webidl.converters.CryptoKey(baseKey, {
      prefix,
      context: "Argument 2",
    });
    derivedKeyType = webidl.converters.AlgorithmIdentifier(derivedKeyType, {
      prefix,
      context: "Argument 3",
    });
    extractable = webidl.converters["boolean"](extractable, {
      prefix,
      context: "Argument 4",
    });
    keyUsages = webidl.converters["sequence<KeyUsage>"](keyUsages, {
      prefix,
      context: "Argument 5",
    });

    // 2-3.
    const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits");

    // 4-5.
    const normalizedDerivedKeyAlgorithmImport = normalizeAlgorithm(
      derivedKeyType,
      "importKey",
    );

    // 6-7.
    const normalizedDerivedKeyAlgorithmLength = normalizeAlgorithm(
      derivedKeyType,
      "get key length",
    );

    // 8-10.

    // 11.
    if (normalizedAlgorithm.name !== baseKey[_algorithm].name) {
      throw new DOMException(
        "Invalid algorithm name",
        "InvalidAccessError",
      );
    }

    // 12.
    if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveKey")) {
      throw new DOMException(
        "baseKey usages does not contain `deriveKey`",
        "InvalidAccessError",
      );
    }

    // 13.
    const length = getKeyLength(normalizedDerivedKeyAlgorithmLength);

    // 14.
    const secret = await this.deriveBits(
      normalizedAlgorithm,
      baseKey,
      length,
    );

    // 15.
    const result = await this.importKey(
      "raw",
      secret,
      normalizedDerivedKeyAlgorithmImport,
      extractable,
      keyUsages,
    );

    // 16.
    if (
      ArrayPrototypeIncludes(["private", "secret"], result[_type]) &&
      keyUsages.length == 0
    ) {
      throw new SyntaxError("Invalid key usages");
    }
    // 17.
    return result;
  }

  /**
   * @param {string} algorithm
   * @param {CryptoKey} key
   * @param {BufferSource} signature
   * @param {BufferSource} data
   * @returns {Promise<boolean>}
   */
  async verify(algorithm, key, signature, data) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'verify' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 4, { prefix });
    algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
      prefix,
      context: "Argument 1",
    });
    key = webidl.converters.CryptoKey(key, {
      prefix,
      context: "Argument 2",
    });
    signature = webidl.converters.BufferSource(signature, {
      prefix,
      context: "Argument 3",
    });
    data = webidl.converters.BufferSource(data, {
      prefix,
      context: "Argument 4",
    });

    // 2.
    signature = copyBuffer(signature);

    // 3.
    data = copyBuffer(data);

    const normalizedAlgorithm = normalizeAlgorithm(algorithm, "verify");

    const handle = key[_handle];
    const keyData = WeakMapPrototypeGet(KEY_STORE, handle);

    if (normalizedAlgorithm.name !== key[_algorithm].name) {
      throw new DOMException(
        "Verifying algorithm doesn't match key algorithm.",
        "InvalidAccessError",
      );
    }

    if (!ArrayPrototypeIncludes(key[_usages], "verify")) {
      throw new DOMException(
        "Key does not support the 'verify' operation.",
        "InvalidAccessError",
      );
    }

    switch (normalizedAlgorithm.name) {
      case "RSASSA-PKCS1-v1_5": {
        if (key[_type] !== "public") {
          throw new DOMException(
            "Key type not supported",
            "InvalidAccessError",
          );
        }

        const hashAlgorithm = key[_algorithm].hash.name;
        return await core.opAsync("op_crypto_verify_key", {
          key: keyData,
          algorithm: "RSASSA-PKCS1-v1_5",
          hash: hashAlgorithm,
          signature,
        }, data);
      }
      case "RSA-PSS": {
        if (key[_type] !== "public") {
          throw new DOMException(
            "Key type not supported",
            "InvalidAccessError",
          );
        }

        const hashAlgorithm = key[_algorithm].hash.name;
        return await core.opAsync("op_crypto_verify_key", {
          key: keyData,
          algorithm: "RSA-PSS",
          hash: hashAlgorithm,
          signature,
        }, data);
      }
      case "HMAC": {
        const hash = key[_algorithm].hash.name;
        return await core.opAsync("op_crypto_verify_key", {
          key: keyData,
          algorithm: "HMAC",
          hash,
          signature,
        }, data);
      }
      case "ECDSA": {
        // 1.
        if (key[_type] !== "public") {
          throw new DOMException(
            "Key type not supported",
            "InvalidAccessError",
          );
        }
        // 2.
        const hash = normalizedAlgorithm.hash.name;

        // 3-8.
        return await core.opAsync("op_crypto_verify_key", {
          key: keyData,
          algorithm: "ECDSA",
          hash,
          signature,
          namedCurve: key[_algorithm].namedCurve,
        }, data);
      }
      case "Ed25519": {
        // 1.
        if (key[_type] !== "public") {
          throw new DOMException(
            "Key type not supported",
            "InvalidAccessError",
          );
        }

        return ops.op_verify_ed25519(keyData, data, signature);
      }
    }

    throw new TypeError("unreachable");
  }

  /**
   * @param {string} algorithm
   * @param {boolean} extractable
   * @param {KeyUsage[]} keyUsages
   * @returns {Promise<any>}
   */
  async wrapKey(format, key, wrappingKey, wrapAlgorithm) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'wrapKey' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 4, { prefix });
    format = webidl.converters.KeyFormat(format, {
      prefix,
      context: "Argument 1",
    });
    key = webidl.converters.CryptoKey(key, {
      prefix,
      context: "Argument 2",
    });
    wrappingKey = webidl.converters.CryptoKey(wrappingKey, {
      prefix,
      context: "Argument 3",
    });
    wrapAlgorithm = webidl.converters.AlgorithmIdentifier(wrapAlgorithm, {
      prefix,
      context: "Argument 4",
    });

    let normalizedAlgorithm;

    try {
      // 2.
      normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "wrapKey");
    } catch (_) {
      // 3.
      normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "encrypt");
    }

    // 8.
    if (normalizedAlgorithm.name !== wrappingKey[_algorithm].name) {
      throw new DOMException(
        "Wrapping algorithm doesn't match key algorithm.",
        "InvalidAccessError",
      );
    }

    // 9.
    if (!ArrayPrototypeIncludes(wrappingKey[_usages], "wrapKey")) {
      throw new DOMException(
        "Key does not support the 'wrapKey' operation.",
        "InvalidAccessError",
      );
    }

    // 10. NotSupportedError will be thrown in step 12.
    // 11.
    if (key[_extractable] === false) {
      throw new DOMException(
        "Key is not extractable",
        "InvalidAccessError",
      );
    }

    // 12.
    const exportedKey = await this.exportKey(format, key);

    let bytes;
    // 13.
    if (format !== "jwk") {
      bytes = new Uint8Array(exportedKey);
    } else {
      const jwk = JSONStringify(exportedKey);
      const ret = new Uint8Array(jwk.length);
      for (let i = 0; i < jwk.length; i++) {
        ret[i] = StringPrototypeCharCodeAt(jwk, i);
      }
      bytes = ret;
    }

    // 14-15.
    if (
      supportedAlgorithms["wrapKey"][normalizedAlgorithm.name] !== undefined
    ) {
      const handle = wrappingKey[_handle];
      const keyData = WeakMapPrototypeGet(KEY_STORE, handle);

      switch (normalizedAlgorithm.name) {
        case "AES-KW": {
          const cipherText = await ops.op_crypto_wrap_key({
            key: keyData,
            algorithm: normalizedAlgorithm.name,
          }, bytes);

          // 4.
          return cipherText.buffer;
        }
        default: {
          throw new DOMException(
            "Not implemented",
            "NotSupportedError",
          );
        }
      }
    } else if (
      supportedAlgorithms["encrypt"][normalizedAlgorithm.name] !== undefined
    ) {
      // must construct a new key, since keyUsages is ["wrapKey"] and not ["encrypt"]
      return await encrypt(
        normalizedAlgorithm,
        constructKey(
          wrappingKey[_type],
          wrappingKey[_extractable],
          ["encrypt"],
          wrappingKey[_algorithm],
          wrappingKey[_handle],
        ),
        bytes,
      );
    } else {
      throw new DOMException(
        "Algorithm not supported",
        "NotSupportedError",
      );
    }
  }
  /**
   * @param {string} format
   * @param {BufferSource} wrappedKey
   * @param {CryptoKey} unwrappingKey
   * @param {AlgorithmIdentifier} unwrapAlgorithm
   * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm
   * @param {boolean} extractable
   * @param {KeyUsage[]} keyUsages
   * @returns {Promise<CryptoKey>}
   */
  async unwrapKey(
    format,
    wrappedKey,
    unwrappingKey,
    unwrapAlgorithm,
    unwrappedKeyAlgorithm,
    extractable,
    keyUsages,
  ) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'unwrapKey' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 7, { prefix });
    format = webidl.converters.KeyFormat(format, {
      prefix,
      context: "Argument 1",
    });
    wrappedKey = webidl.converters.BufferSource(wrappedKey, {
      prefix,
      context: "Argument 2",
    });
    unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, {
      prefix,
      context: "Argument 3",
    });
    unwrapAlgorithm = webidl.converters.AlgorithmIdentifier(unwrapAlgorithm, {
      prefix,
      context: "Argument 4",
    });
    unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(
      unwrappedKeyAlgorithm,
      {
        prefix,
        context: "Argument 5",
      },
    );
    extractable = webidl.converters.boolean(extractable, {
      prefix,
      context: "Argument 6",
    });
    keyUsages = webidl.converters["sequence<KeyUsage>"](keyUsages, {
      prefix,
      context: "Argument 7",
    });

    // 2.
    wrappedKey = copyBuffer(wrappedKey);

    let normalizedAlgorithm;

    try {
      // 3.
      normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "unwrapKey");
    } catch (_) {
      // 4.
      normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "decrypt");
    }

    // 6.
    const normalizedKeyAlgorithm = normalizeAlgorithm(
      unwrappedKeyAlgorithm,
      "importKey",
    );

    // 11.
    if (normalizedAlgorithm.name !== unwrappingKey[_algorithm].name) {
      throw new DOMException(
        "Unwrapping algorithm doesn't match key algorithm.",
        "InvalidAccessError",
      );
    }

    // 12.
    if (!ArrayPrototypeIncludes(unwrappingKey[_usages], "unwrapKey")) {
      throw new DOMException(
        "Key does not support the 'unwrapKey' operation.",
        "InvalidAccessError",
      );
    }

    // 13.
    let key;
    if (
      supportedAlgorithms["unwrapKey"][normalizedAlgorithm.name] !== undefined
    ) {
      const handle = unwrappingKey[_handle];
      const keyData = WeakMapPrototypeGet(KEY_STORE, handle);

      switch (normalizedAlgorithm.name) {
        case "AES-KW": {
          const plainText = await ops.op_crypto_unwrap_key({
            key: keyData,
            algorithm: normalizedAlgorithm.name,
          }, wrappedKey);

          // 4.
          key = plainText.buffer;
          break;
        }
        default: {
          throw new DOMException(
            "Not implemented",
            "NotSupportedError",
          );
        }
      }
    } else if (
      supportedAlgorithms["decrypt"][normalizedAlgorithm.name] !== undefined
    ) {
      // must construct a new key, since keyUsages is ["unwrapKey"] and not ["decrypt"]
      key = await this.decrypt(
        normalizedAlgorithm,
        constructKey(
          unwrappingKey[_type],
          unwrappingKey[_extractable],
          ["decrypt"],
          unwrappingKey[_algorithm],
          unwrappingKey[_handle],
        ),
        wrappedKey,
      );
    } else {
      throw new DOMException(
        "Algorithm not supported",
        "NotSupportedError",
      );
    }

    let bytes;
    // 14.
    if (format !== "jwk") {
      bytes = key;
    } else {
      const k = new Uint8Array(key);
      let str = "";
      for (let i = 0; i < k.length; i++) {
        str += StringFromCharCode(k[i]);
      }
      bytes = JSONParse(str);
    }

    // 15.
    const result = await this.importKey(
      format,
      bytes,
      normalizedKeyAlgorithm,
      extractable,
      keyUsages,
    );
    // 16.
    if (
      (result[_type] == "secret" || result[_type] == "private") &&
      keyUsages.length == 0
    ) {
      throw new SyntaxError("Invalid key type.");
    }
    // 17.
    result[_extractable] = extractable;
    // 18.
    result[_usages] = usageIntersection(keyUsages, recognisedUsages);
    // 19.
    return result;
  }

  /**
   * @param {string} algorithm
   * @param {boolean} extractable
   * @param {KeyUsage[]} keyUsages
   * @returns {Promise<any>}
   */
  async generateKey(algorithm, extractable, keyUsages) {
    webidl.assertBranded(this, SubtleCryptoPrototype);
    const prefix = "Failed to execute 'generateKey' on 'SubtleCrypto'";
    webidl.requiredArguments(arguments.length, 3, { prefix });
    algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
      prefix,
      context: "Argument 1",
    });
    extractable = webidl.converters["boolean"](extractable, {
      prefix,
      context: "Argument 2",
    });
    keyUsages = webidl.converters["sequence<KeyUsage>"](keyUsages, {
      prefix,
      context: "Argument 3",
    });

    const usages = keyUsages;

    const normalizedAlgorithm = normalizeAlgorithm(algorithm, "generateKey");
    const result = await generateKey(
      normalizedAlgorithm,
      extractable,
      usages,
    );

    if (ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result)) {
      const type = result[_type];
      if ((type === "secret" || type === "private") && usages.length === 0) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }
    } else if (
      ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result.privateKey)
    ) {
      if (result.privateKey[_usages].length === 0) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }
    }

    return result;
  }
}
const SubtleCryptoPrototype = SubtleCrypto.prototype;

async function generateKey(normalizedAlgorithm, extractable, usages) {
  const algorithmName = normalizedAlgorithm.name;

  switch (algorithmName) {
    case "RSASSA-PKCS1-v1_5":
    case "RSA-PSS": {
      // 1.
      if (
        ArrayPrototypeFind(
          usages,
          (u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 2.
      const keyData = await core.opAsync(
        "op_crypto_generate_key",
        {
          algorithm: "RSA",
          modulusLength: normalizedAlgorithm.modulusLength,
          publicExponent: normalizedAlgorithm.publicExponent,
        },
      );
      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, {
        type: "private",
        data: keyData,
      });

      // 4-8.
      const algorithm = {
        name: algorithmName,
        modulusLength: normalizedAlgorithm.modulusLength,
        publicExponent: normalizedAlgorithm.publicExponent,
        hash: normalizedAlgorithm.hash,
      };

      // 9-13.
      const publicKey = constructKey(
        "public",
        true,
        usageIntersection(usages, ["verify"]),
        algorithm,
        handle,
      );

      // 14-18.
      const privateKey = constructKey(
        "private",
        extractable,
        usageIntersection(usages, ["sign"]),
        algorithm,
        handle,
      );

      // 19-22.
      return { publicKey, privateKey };
    }
    case "RSA-OAEP": {
      if (
        ArrayPrototypeFind(
          usages,
          (u) =>
            !ArrayPrototypeIncludes([
              "encrypt",
              "decrypt",
              "wrapKey",
              "unwrapKey",
            ], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 2.
      const keyData = await core.opAsync(
        "op_crypto_generate_key",
        {
          algorithm: "RSA",
          modulusLength: normalizedAlgorithm.modulusLength,
          publicExponent: normalizedAlgorithm.publicExponent,
        },
      );
      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, {
        type: "private",
        data: keyData,
      });

      // 4-8.
      const algorithm = {
        name: algorithmName,
        modulusLength: normalizedAlgorithm.modulusLength,
        publicExponent: normalizedAlgorithm.publicExponent,
        hash: normalizedAlgorithm.hash,
      };

      // 9-13.
      const publicKey = constructKey(
        "public",
        true,
        usageIntersection(usages, ["encrypt", "wrapKey"]),
        algorithm,
        handle,
      );

      // 14-18.
      const privateKey = constructKey(
        "private",
        extractable,
        usageIntersection(usages, ["decrypt", "unwrapKey"]),
        algorithm,
        handle,
      );

      // 19-22.
      return { publicKey, privateKey };
    }
    case "ECDSA": {
      const namedCurve = normalizedAlgorithm.namedCurve;

      // 1.
      if (
        ArrayPrototypeFind(
          usages,
          (u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 2-3.
      const handle = {};
      if (
        ArrayPrototypeIncludes(
          supportedNamedCurves,
          namedCurve,
        )
      ) {
        const keyData = await core.opAsync("op_crypto_generate_key", {
          algorithm: "EC",
          namedCurve,
        });
        WeakMapPrototypeSet(KEY_STORE, handle, {
          type: "private",
          data: keyData,
        });
      } else {
        throw new DOMException("Curve not supported", "NotSupportedError");
      }

      // 4-6.
      const algorithm = {
        name: algorithmName,
        namedCurve,
      };

      // 7-11.
      const publicKey = constructKey(
        "public",
        true,
        usageIntersection(usages, ["verify"]),
        algorithm,
        handle,
      );

      // 12-16.
      const privateKey = constructKey(
        "private",
        extractable,
        usageIntersection(usages, ["sign"]),
        algorithm,
        handle,
      );

      // 17-20.
      return { publicKey, privateKey };
    }
    case "ECDH": {
      const namedCurve = normalizedAlgorithm.namedCurve;

      // 1.
      if (
        ArrayPrototypeFind(
          usages,
          (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 2-3.
      const handle = {};
      if (
        ArrayPrototypeIncludes(
          supportedNamedCurves,
          namedCurve,
        )
      ) {
        const keyData = await core.opAsync("op_crypto_generate_key", {
          algorithm: "EC",
          namedCurve,
        });
        WeakMapPrototypeSet(KEY_STORE, handle, {
          type: "private",
          data: keyData,
        });
      } else {
        throw new DOMException("Curve not supported", "NotSupportedError");
      }

      // 4-6.
      const algorithm = {
        name: algorithmName,
        namedCurve,
      };

      // 7-11.
      const publicKey = constructKey(
        "public",
        true,
        usageIntersection(usages, []),
        algorithm,
        handle,
      );

      // 12-16.
      const privateKey = constructKey(
        "private",
        extractable,
        usageIntersection(usages, ["deriveKey", "deriveBits"]),
        algorithm,
        handle,
      );

      // 17-20.
      return { publicKey, privateKey };
    }
    case "AES-CTR":
    case "AES-CBC":
    case "AES-GCM": {
      // 1.
      if (
        ArrayPrototypeFind(
          usages,
          (u) =>
            !ArrayPrototypeIncludes([
              "encrypt",
              "decrypt",
              "wrapKey",
              "unwrapKey",
            ], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      return generateKeyAES(normalizedAlgorithm, extractable, usages);
    }
    case "AES-KW": {
      // 1.
      if (
        ArrayPrototypeFind(
          usages,
          (u) => !ArrayPrototypeIncludes(["wrapKey", "unwrapKey"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      return generateKeyAES(normalizedAlgorithm, extractable, usages);
    }
    case "X25519": {
      if (
        ArrayPrototypeFind(
          usages,
          (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }
      const privateKeyData = new Uint8Array(32);
      const publicKeyData = new Uint8Array(32);
      ops.op_generate_x25519_keypair(privateKeyData, publicKeyData);

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);

      const publicHandle = {};
      WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData);

      const algorithm = {
        name: algorithmName,
      };

      const publicKey = constructKey(
        "public",
        true,
        usageIntersection(usages, []),
        algorithm,
        publicHandle,
      );

      const privateKey = constructKey(
        "private",
        extractable,
        usageIntersection(usages, ["deriveKey", "deriveBits"]),
        algorithm,
        handle,
      );

      return { publicKey, privateKey };
    }
    case "Ed25519": {
      if (
        ArrayPrototypeFind(
          usages,
          (u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      const ED25519_SEED_LEN = 32;
      const ED25519_PUBLIC_KEY_LEN = 32;
      const privateKeyData = new Uint8Array(ED25519_SEED_LEN);
      const publicKeyData = new Uint8Array(ED25519_PUBLIC_KEY_LEN);
      if (
        !ops.op_generate_ed25519_keypair(privateKeyData, publicKeyData)
      ) {
        throw new DOMException("Failed to generate key", "OperationError");
      }

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);

      const publicHandle = {};
      WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData);

      const algorithm = {
        name: algorithmName,
      };

      const publicKey = constructKey(
        "public",
        true,
        usageIntersection(usages, ["verify"]),
        algorithm,
        publicHandle,
      );

      const privateKey = constructKey(
        "private",
        extractable,
        usageIntersection(usages, ["sign"]),
        algorithm,
        handle,
      );

      return { publicKey, privateKey };
    }
    case "HMAC": {
      // 1.
      if (
        ArrayPrototypeFind(
          usages,
          (u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 2.
      let length;
      if (normalizedAlgorithm.length === undefined) {
        length = null;
      } else if (normalizedAlgorithm.length !== 0) {
        length = normalizedAlgorithm.length;
      } else {
        throw new DOMException("Invalid length", "OperationError");
      }

      // 3-4.
      const keyData = await core.opAsync("op_crypto_generate_key", {
        algorithm: "HMAC",
        hash: normalizedAlgorithm.hash.name,
        length,
      });
      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, {
        type: "secret",
        data: keyData,
      });

      // 6-10.
      const algorithm = {
        name: algorithmName,
        hash: {
          name: normalizedAlgorithm.hash.name,
        },
        length: keyData.byteLength * 8,
      };

      // 5, 11-13.
      const key = constructKey(
        "secret",
        extractable,
        usages,
        algorithm,
        handle,
      );

      // 14.
      return key;
    }
  }
}

function importKeyEd25519(
  format,
  keyData,
  extractable,
  keyUsages,
) {
  switch (format) {
    case "raw": {
      // 1.
      if (
        ArrayPrototypeFind(
          keyUsages,
          (u) => !ArrayPrototypeIncludes(["verify"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, keyData);

      // 2-3.
      const algorithm = {
        name: "Ed25519",
      };

      // 4-6.
      return constructKey(
        "public",
        extractable,
        usageIntersection(keyUsages, recognisedUsages),
        algorithm,
        handle,
      );
    }
    case "spki": {
      // 1.
      if (
        ArrayPrototypeFind(
          keyUsages,
          (u) => !ArrayPrototypeIncludes(["verify"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      const publicKeyData = new Uint8Array(32);
      if (!ops.op_import_spki_ed25519(keyData, publicKeyData)) {
        throw new DOMException("Invalid key data", "DataError");
      }

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);

      const algorithm = {
        name: "Ed25519",
      };

      return constructKey(
        "public",
        extractable,
        usageIntersection(keyUsages, recognisedUsages),
        algorithm,
        handle,
      );
    }
    case "pkcs8": {
      // 1.
      if (
        ArrayPrototypeFind(
          keyUsages,
          (u) => !ArrayPrototypeIncludes(["sign"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      const privateKeyData = new Uint8Array(32);
      if (!ops.op_import_pkcs8_ed25519(keyData, privateKeyData)) {
        throw new DOMException("Invalid key data", "DataError");
      }

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);

      const algorithm = {
        name: "Ed25519",
      };

      return constructKey(
        "private",
        extractable,
        usageIntersection(keyUsages, recognisedUsages),
        algorithm,
        handle,
      );
    }
    case "jwk": {
      // 1.
      const jwk = keyData;

      // 2.
      if (jwk.d !== undefined) {
        if (
          ArrayPrototypeFind(
            keyUsages,
            (u) =>
              !ArrayPrototypeIncludes(
                ["sign"],
                u,
              ),
          ) !== undefined
        ) {
          throw new DOMException("Invalid key usages", "SyntaxError");
        }
      } else {
        if (
          ArrayPrototypeFind(
            keyUsages,
            (u) =>
              !ArrayPrototypeIncludes(
                ["verify"],
                u,
              ),
          ) !== undefined
        ) {
          throw new DOMException("Invalid key usages", "SyntaxError");
        }
      }

      // 3.
      if (jwk.kty !== "OKP") {
        throw new DOMException("Invalid key type", "DataError");
      }

      // 4.
      if (jwk.crv !== "Ed25519") {
        throw new DOMException("Invalid curve", "DataError");
      }

      // 5.
      if (jwk.alg !== undefined && jwk.alg !== "EdDSA") {
        throw new DOMException("Invalid algorithm", "DataError");
      }

      // 6.
      if (
        keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig"
      ) {
        throw new DOMException("Invalid key usage", "DataError");
      }

      // 7.
      if (jwk.key_ops !== undefined) {
        if (
          ArrayPrototypeFind(
            jwk.key_ops,
            (u) => !ArrayPrototypeIncludes(recognisedUsages, u),
          ) !== undefined
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }

        if (
          !ArrayPrototypeEvery(
            jwk.key_ops,
            (u) => ArrayPrototypeIncludes(keyUsages, u),
          )
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }
      }

      // 8.
      if (jwk.ext !== undefined && jwk.ext === false && extractable) {
        throw new DOMException("Invalid key extractability", "DataError");
      }

      // 9.
      if (jwk.d !== undefined) {
        // https://www.rfc-editor.org/rfc/rfc8037#section-2
        const privateKeyData = ops.op_crypto_base64url_decode(jwk.d);

        const handle = {};
        WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);

        const algorithm = {
          name: "Ed25519",
        };

        return constructKey(
          "private",
          extractable,
          usageIntersection(keyUsages, recognisedUsages),
          algorithm,
          handle,
        );
      } else {
        // https://www.rfc-editor.org/rfc/rfc8037#section-2
        const publicKeyData = ops.op_crypto_base64url_decode(jwk.x);

        const handle = {};
        WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);

        const algorithm = {
          name: "Ed25519",
        };

        return constructKey(
          "public",
          extractable,
          usageIntersection(keyUsages, recognisedUsages),
          algorithm,
          handle,
        );
      }
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

function importKeyX25519(
  format,
  keyData,
  extractable,
  keyUsages,
) {
  switch (format) {
    case "raw": {
      // 1.
      if (keyUsages.length > 0) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, keyData);

      // 2-3.
      const algorithm = {
        name: "X25519",
      };

      // 4-6.
      return constructKey(
        "public",
        extractable,
        [],
        algorithm,
        handle,
      );
    }
    case "spki": {
      // 1.
      if (keyUsages.length > 0) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      const publicKeyData = new Uint8Array(32);
      if (!ops.op_import_spki_x25519(keyData, publicKeyData)) {
        throw new DOMException("Invalid key data", "DataError");
      }

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);

      const algorithm = {
        name: "X25519",
      };

      return constructKey(
        "public",
        extractable,
        [],
        algorithm,
        handle,
      );
    }
    case "pkcs8": {
      // 1.
      if (
        ArrayPrototypeFind(
          keyUsages,
          (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      const privateKeyData = new Uint8Array(32);
      if (!ops.op_import_pkcs8_x25519(keyData, privateKeyData)) {
        throw new DOMException("Invalid key data", "DataError");
      }

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);

      const algorithm = {
        name: "X25519",
      };

      return constructKey(
        "private",
        extractable,
        usageIntersection(keyUsages, recognisedUsages),
        algorithm,
        handle,
      );
    }
    case "jwk": {
      // 1.
      const jwk = keyData;

      // 2.
      if (jwk.d !== undefined) {
        if (
          ArrayPrototypeFind(
            keyUsages,
            (u) =>
              !ArrayPrototypeIncludes(
                ["deriveKey", "deriveBits"],
                u,
              ),
          ) !== undefined
        ) {
          throw new DOMException("Invalid key usages", "SyntaxError");
        }
      }

      // 3.
      if (jwk.d === undefined && keyUsages.length > 0) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 4.
      if (jwk.kty !== "OKP") {
        throw new DOMException("Invalid key type", "DataError");
      }

      // 5.
      if (jwk.crv !== "X25519") {
        throw new DOMException("Invalid curve", "DataError");
      }

      // 6.
      if (keyUsages.length > 0 && jwk.use !== undefined) {
        if (jwk.use !== "enc") {
          throw new DOMException("Invalid key use", "DataError");
        }
      }

      // 7.
      if (jwk.key_ops !== undefined) {
        if (
          ArrayPrototypeFind(
            jwk.key_ops,
            (u) => !ArrayPrototypeIncludes(recognisedUsages, u),
          ) !== undefined
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }

        if (
          !ArrayPrototypeEvery(
            jwk.key_ops,
            (u) => ArrayPrototypeIncludes(keyUsages, u),
          )
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }
      }

      // 8.
      if (jwk.ext !== undefined && jwk.ext === false && extractable) {
        throw new DOMException("Invalid key extractability", "DataError");
      }

      // 9.
      if (jwk.d !== undefined) {
        // https://www.rfc-editor.org/rfc/rfc8037#section-2
        const privateKeyData = ops.op_crypto_base64url_decode(jwk.d);

        const handle = {};
        WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);

        const algorithm = {
          name: "X25519",
        };

        return constructKey(
          "private",
          extractable,
          usageIntersection(keyUsages, ["deriveKey", "deriveBits"]),
          algorithm,
          handle,
        );
      } else {
        // https://www.rfc-editor.org/rfc/rfc8037#section-2
        const publicKeyData = ops.op_crypto_base64url_decode(jwk.x);

        const handle = {};
        WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);

        const algorithm = {
          name: "X25519",
        };

        return constructKey(
          "public",
          extractable,
          [],
          algorithm,
          handle,
        );
      }
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

function exportKeyAES(
  format,
  key,
  innerKey,
) {
  switch (format) {
    // 2.
    case "raw": {
      // 1.
      const data = innerKey.data;
      // 2.
      return data.buffer;
    }
    case "jwk": {
      // 1-2.
      const jwk = {
        kty: "oct",
      };

      // 3.
      const data = ops.op_crypto_export_key({
        format: "jwksecret",
        algorithm: "AES",
      }, innerKey);
      ObjectAssign(jwk, data);

      // 4.
      const algorithm = key[_algorithm];
      switch (algorithm.length) {
        case 128:
          jwk.alg = aesJwkAlg[algorithm.name][128];
          break;
        case 192:
          jwk.alg = aesJwkAlg[algorithm.name][192];
          break;
        case 256:
          jwk.alg = aesJwkAlg[algorithm.name][256];
          break;
        default:
          throw new DOMException(
            "Invalid key length",
            "NotSupportedError",
          );
      }

      // 5.
      jwk.key_ops = key.usages;

      // 6.
      jwk.ext = key[_extractable];

      // 7.
      return jwk;
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

function importKeyAES(
  format,
  normalizedAlgorithm,
  keyData,
  extractable,
  keyUsages,
  supportedKeyUsages,
) {
  // 1.
  if (
    ArrayPrototypeFind(
      keyUsages,
      (u) => !ArrayPrototypeIncludes(supportedKeyUsages, u),
    ) !== undefined
  ) {
    throw new DOMException("Invalid key usages", "SyntaxError");
  }

  const algorithmName = normalizedAlgorithm.name;

  // 2.
  let data = keyData;

  switch (format) {
    case "raw": {
      // 2.
      if (
        !ArrayPrototypeIncludes([128, 192, 256], keyData.byteLength * 8)
      ) {
        throw new DOMException("Invalid key length", "Datarror");
      }

      break;
    }
    case "jwk": {
      // 1.
      const jwk = keyData;

      // 2.
      if (jwk.kty !== "oct") {
        throw new DOMException(
          "'kty' property of JsonWebKey must be 'oct'",
          "DataError",
        );
      }

      // Section 6.4.1 of RFC7518
      if (jwk.k === undefined) {
        throw new DOMException(
          "'k' property of JsonWebKey must be present",
          "DataError",
        );
      }

      // 4.
      const { rawData } = ops.op_crypto_import_key(
        { algorithm: "AES" },
        { jwkSecret: jwk },
      );
      data = rawData.data;

      // 5.
      switch (data.byteLength * 8) {
        case 128:
          if (
            jwk.alg !== undefined &&
            jwk.alg !== aesJwkAlg[algorithmName][128]
          ) {
            throw new DOMException("Invalid algorithm", "DataError");
          }
          break;
        case 192:
          if (
            jwk.alg !== undefined &&
            jwk.alg !== aesJwkAlg[algorithmName][192]
          ) {
            throw new DOMException("Invalid algorithm", "DataError");
          }
          break;
        case 256:
          if (
            jwk.alg !== undefined &&
            jwk.alg !== aesJwkAlg[algorithmName][256]
          ) {
            throw new DOMException("Invalid algorithm", "DataError");
          }
          break;
        default:
          throw new DOMException(
            "Invalid key length",
            "DataError",
          );
      }

      // 6.
      if (
        keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "enc"
      ) {
        throw new DOMException("Invalid key usages", "DataError");
      }

      // 7.
      // Section 4.3 of RFC7517
      if (jwk.key_ops !== undefined) {
        if (
          ArrayPrototypeFind(
            jwk.key_ops,
            (u) => !ArrayPrototypeIncludes(recognisedUsages, u),
          ) !== undefined
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }

        if (
          !ArrayPrototypeEvery(
            jwk.key_ops,
            (u) => ArrayPrototypeIncludes(keyUsages, u),
          )
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }
      }

      // 8.
      if (jwk.ext === false && extractable === true) {
        throw new DOMException(
          "'ext' property of JsonWebKey must not be false if extractable is true",
          "DataError",
        );
      }

      break;
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }

  const handle = {};
  WeakMapPrototypeSet(KEY_STORE, handle, {
    type: "secret",
    data,
  });

  // 4-7.
  const algorithm = {
    name: algorithmName,
    length: data.byteLength * 8,
  };

  const key = constructKey(
    "secret",
    extractable,
    usageIntersection(keyUsages, recognisedUsages),
    algorithm,
    handle,
  );

  // 8.
  return key;
}

function importKeyHMAC(
  format,
  normalizedAlgorithm,
  keyData,
  extractable,
  keyUsages,
) {
  // 2.
  if (
    ArrayPrototypeFind(
      keyUsages,
      (u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
    ) !== undefined
  ) {
    throw new DOMException("Invalid key usages", "SyntaxError");
  }

  // 3.
  let hash;
  let data;

  // 4. https://w3c.github.io/webcrypto/#hmac-operations
  switch (format) {
    case "raw": {
      data = keyData;
      hash = normalizedAlgorithm.hash;
      break;
    }
    case "jwk": {
      const jwk = keyData;

      // 2.
      if (jwk.kty !== "oct") {
        throw new DOMException(
          "'kty' property of JsonWebKey must be 'oct'",
          "DataError",
        );
      }

      // Section 6.4.1 of RFC7518
      if (jwk.k === undefined) {
        throw new DOMException(
          "'k' property of JsonWebKey must be present",
          "DataError",
        );
      }

      // 4.
      const { rawData } = ops.op_crypto_import_key(
        { algorithm: "HMAC" },
        { jwkSecret: jwk },
      );
      data = rawData.data;

      // 5.
      hash = normalizedAlgorithm.hash;

      // 6.
      switch (hash.name) {
        case "SHA-1": {
          if (jwk.alg !== undefined && jwk.alg !== "HS1") {
            throw new DOMException(
              "'alg' property of JsonWebKey must be 'HS1'",
              "DataError",
            );
          }
          break;
        }
        case "SHA-256": {
          if (jwk.alg !== undefined && jwk.alg !== "HS256") {
            throw new DOMException(
              "'alg' property of JsonWebKey must be 'HS256'",
              "DataError",
            );
          }
          break;
        }
        case "SHA-384": {
          if (jwk.alg !== undefined && jwk.alg !== "HS384") {
            throw new DOMException(
              "'alg' property of JsonWebKey must be 'HS384'",
              "DataError",
            );
          }
          break;
        }
        case "SHA-512": {
          if (jwk.alg !== undefined && jwk.alg !== "HS512") {
            throw new DOMException(
              "'alg' property of JsonWebKey must be 'HS512'",
              "DataError",
            );
          }
          break;
        }
        default:
          throw new TypeError("unreachable");
      }

      // 7.
      if (
        keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig"
      ) {
        throw new DOMException(
          "'use' property of JsonWebKey must be 'sig'",
          "DataError",
        );
      }

      // 8.
      // Section 4.3 of RFC7517
      if (jwk.key_ops !== undefined) {
        if (
          ArrayPrototypeFind(
            jwk.key_ops,
            (u) => !ArrayPrototypeIncludes(recognisedUsages, u),
          ) !== undefined
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }

        if (
          !ArrayPrototypeEvery(
            jwk.key_ops,
            (u) => ArrayPrototypeIncludes(keyUsages, u),
          )
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }
      }

      // 9.
      if (jwk.ext === false && extractable === true) {
        throw new DOMException(
          "'ext' property of JsonWebKey must not be false if extractable is true",
          "DataError",
        );
      }

      break;
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }

  // 5.
  let length = data.byteLength * 8;
  // 6.
  if (length === 0) {
    throw new DOMException("Key length is zero", "DataError");
  }
  // 7.
  if (normalizedAlgorithm.length !== undefined) {
    if (
      normalizedAlgorithm.length > length ||
      normalizedAlgorithm.length <= (length - 8)
    ) {
      throw new DOMException(
        "Key length is invalid",
        "DataError",
      );
    }
    length = normalizedAlgorithm.length;
  }

  const handle = {};
  WeakMapPrototypeSet(KEY_STORE, handle, {
    type: "secret",
    data,
  });

  const algorithm = {
    name: "HMAC",
    length,
    hash,
  };

  const key = constructKey(
    "secret",
    extractable,
    usageIntersection(keyUsages, recognisedUsages),
    algorithm,
    handle,
  );

  return key;
}

function importKeyEC(
  format,
  normalizedAlgorithm,
  keyData,
  extractable,
  keyUsages,
) {
  const supportedUsages = SUPPORTED_KEY_USAGES[normalizedAlgorithm.name];

  switch (format) {
    case "raw": {
      // 1.
      if (
        !ArrayPrototypeIncludes(
          supportedNamedCurves,
          normalizedAlgorithm.namedCurve,
        )
      ) {
        throw new DOMException(
          "Invalid namedCurve",
          "DataError",
        );
      }

      // 2.
      if (
        ArrayPrototypeFind(
          keyUsages,
          (u) =>
            !ArrayPrototypeIncludes(
              SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public,
              u,
            ),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 3.
      const { rawData } = ops.op_crypto_import_key({
        algorithm: normalizedAlgorithm.name,
        namedCurve: normalizedAlgorithm.namedCurve,
      }, { raw: keyData });

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, rawData);

      // 4-5.
      const algorithm = {
        name: normalizedAlgorithm.name,
        namedCurve: normalizedAlgorithm.namedCurve,
      };

      // 6-8.
      const key = constructKey(
        "public",
        extractable,
        usageIntersection(keyUsages, recognisedUsages),
        algorithm,
        handle,
      );

      return key;
    }
    case "pkcs8": {
      // 1.
      if (
        ArrayPrototypeFind(
          keyUsages,
          (u) =>
            !ArrayPrototypeIncludes(
              SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private,
              u,
            ),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 2-9.
      const { rawData } = ops.op_crypto_import_key({
        algorithm: normalizedAlgorithm.name,
        namedCurve: normalizedAlgorithm.namedCurve,
      }, { pkcs8: keyData });

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, rawData);

      const algorithm = {
        name: normalizedAlgorithm.name,
        namedCurve: normalizedAlgorithm.namedCurve,
      };

      const key = constructKey(
        "private",
        extractable,
        usageIntersection(keyUsages, recognisedUsages),
        algorithm,
        handle,
      );

      return key;
    }
    case "spki": {
      // 1.
      if (normalizedAlgorithm.name == "ECDSA") {
        if (
          ArrayPrototypeFind(
            keyUsages,
            (u) =>
              !ArrayPrototypeIncludes(
                SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public,
                u,
              ),
          ) !== undefined
        ) {
          throw new DOMException("Invalid key usages", "SyntaxError");
        }
      } else if (keyUsages.length != 0) {
        throw new DOMException("Key usage must be empty", "SyntaxError");
      }

      // 2-12
      const { rawData } = ops.op_crypto_import_key({
        algorithm: normalizedAlgorithm.name,
        namedCurve: normalizedAlgorithm.namedCurve,
      }, { spki: keyData });

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, rawData);

      const algorithm = {
        name: normalizedAlgorithm.name,
        namedCurve: normalizedAlgorithm.namedCurve,
      };

      // 6-8.
      const key = constructKey(
        "public",
        extractable,
        usageIntersection(keyUsages, recognisedUsages),
        algorithm,
        handle,
      );

      return key;
    }
    case "jwk": {
      const jwk = keyData;

      const keyType = (jwk.d !== undefined) ? "private" : "public";

      // 2.
      if (
        ArrayPrototypeFind(
          keyUsages,
          (u) => !ArrayPrototypeIncludes(supportedUsages[keyType], u),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 3.
      if (jwk.kty !== "EC") {
        throw new DOMException(
          "'kty' property of JsonWebKey must be 'EC'",
          "DataError",
        );
      }

      // 4.
      if (
        keyUsages.length > 0 && jwk.use !== undefined &&
        jwk.use !== supportedUsages.jwkUse
      ) {
        throw new DOMException(
          `'use' property of JsonWebKey must be '${supportedUsages.jwkUse}'`,
          "DataError",
        );
      }

      // 5.
      // Section 4.3 of RFC7517
      if (jwk.key_ops !== undefined) {
        if (
          ArrayPrototypeFind(
            jwk.key_ops,
            (u) => !ArrayPrototypeIncludes(recognisedUsages, u),
          ) !== undefined
        ) {
          throw new DOMException(
            "'key_ops' member of JsonWebKey is invalid",
            "DataError",
          );
        }

        if (
          !ArrayPrototypeEvery(
            jwk.key_ops,
            (u) => ArrayPrototypeIncludes(keyUsages, u),
          )
        ) {
          throw new DOMException(
            "'key_ops' member of JsonWebKey is invalid",
            "DataError",
          );
        }
      }

      // 6.
      if (jwk.ext === false && extractable === true) {
        throw new DOMException(
          "'ext' property of JsonWebKey must not be false if extractable is true",
          "DataError",
        );
      }

      // 9.
      if (jwk.alg !== undefined && normalizedAlgorithm.name == "ECDSA") {
        let algNamedCurve;

        switch (jwk.alg) {
          case "ES256": {
            algNamedCurve = "P-256";
            break;
          }
          case "ES384": {
            algNamedCurve = "P-384";
            break;
          }
          case "ES512": {
            algNamedCurve = "P-521";
            break;
          }
          default:
            throw new DOMException(
              "Curve algorithm not supported",
              "DataError",
            );
        }

        if (algNamedCurve) {
          if (algNamedCurve !== normalizedAlgorithm.namedCurve) {
            throw new DOMException(
              "Mismatched curve algorithm",
              "DataError",
            );
          }
        }
      }

      // Validate that this is a valid public key.
      if (jwk.x === undefined) {
        throw new DOMException(
          "'x' property of JsonWebKey is required for EC keys",
          "DataError",
        );
      }
      if (jwk.y === undefined) {
        throw new DOMException(
          "'y' property of JsonWebKey is required for EC keys",
          "DataError",
        );
      }

      if (jwk.d !== undefined) {
        // it's also a Private key
        const { rawData } = ops.op_crypto_import_key({
          algorithm: normalizedAlgorithm.name,
          namedCurve: normalizedAlgorithm.namedCurve,
        }, { jwkPrivateEc: jwk });

        const handle = {};
        WeakMapPrototypeSet(KEY_STORE, handle, rawData);

        const algorithm = {
          name: normalizedAlgorithm.name,
          namedCurve: normalizedAlgorithm.namedCurve,
        };

        const key = constructKey(
          "private",
          extractable,
          usageIntersection(keyUsages, recognisedUsages),
          algorithm,
          handle,
        );

        return key;
      } else {
        const { rawData } = ops.op_crypto_import_key({
          algorithm: normalizedAlgorithm.name,
          namedCurve: normalizedAlgorithm.namedCurve,
        }, { jwkPublicEc: jwk });

        const handle = {};
        WeakMapPrototypeSet(KEY_STORE, handle, rawData);

        const algorithm = {
          name: normalizedAlgorithm.name,
          namedCurve: normalizedAlgorithm.namedCurve,
        };

        const key = constructKey(
          "public",
          extractable,
          usageIntersection(keyUsages, recognisedUsages),
          algorithm,
          handle,
        );

        return key;
      }
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

const SUPPORTED_KEY_USAGES = {
  "RSASSA-PKCS1-v1_5": {
    public: ["verify"],
    private: ["sign"],
    jwkUse: "sig",
  },
  "RSA-PSS": {
    public: ["verify"],
    private: ["sign"],
    jwkUse: "sig",
  },
  "RSA-OAEP": {
    public: ["encrypt", "wrapKey"],
    private: ["decrypt", "unwrapKey"],
    jwkUse: "enc",
  },
  "ECDSA": {
    public: ["verify"],
    private: ["sign"],
    jwkUse: "sig",
  },
  "ECDH": {
    public: [],
    private: ["deriveKey", "deriveBits"],
    jwkUse: "enc",
  },
};

function importKeyRSA(
  format,
  normalizedAlgorithm,
  keyData,
  extractable,
  keyUsages,
) {
  switch (format) {
    case "pkcs8": {
      // 1.
      if (
        ArrayPrototypeFind(
          keyUsages,
          (u) =>
            !ArrayPrototypeIncludes(
              SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private,
              u,
            ),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 2-9.
      const { modulusLength, publicExponent, rawData } = ops
        .op_crypto_import_key(
          {
            algorithm: normalizedAlgorithm.name,
            // Needed to perform step 7 without normalization.
            hash: normalizedAlgorithm.hash.name,
          },
          { pkcs8: keyData },
        );

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, rawData);

      const algorithm = {
        name: normalizedAlgorithm.name,
        modulusLength,
        publicExponent,
        hash: normalizedAlgorithm.hash,
      };

      const key = constructKey(
        "private",
        extractable,
        usageIntersection(keyUsages, recognisedUsages),
        algorithm,
        handle,
      );

      return key;
    }
    case "spki": {
      // 1.
      if (
        ArrayPrototypeFind(
          keyUsages,
          (u) =>
            !ArrayPrototypeIncludes(
              SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public,
              u,
            ),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 2-9.
      const { modulusLength, publicExponent, rawData } = ops
        .op_crypto_import_key(
          {
            algorithm: normalizedAlgorithm.name,
            // Needed to perform step 7 without normalization.
            hash: normalizedAlgorithm.hash.name,
          },
          { spki: keyData },
        );

      const handle = {};
      WeakMapPrototypeSet(KEY_STORE, handle, rawData);

      const algorithm = {
        name: normalizedAlgorithm.name,
        modulusLength,
        publicExponent,
        hash: normalizedAlgorithm.hash,
      };

      const key = constructKey(
        "public",
        extractable,
        usageIntersection(keyUsages, recognisedUsages),
        algorithm,
        handle,
      );

      return key;
    }
    case "jwk": {
      // 1.
      const jwk = keyData;

      // 2.
      if (jwk.d !== undefined) {
        if (
          ArrayPrototypeFind(
            keyUsages,
            (u) =>
              !ArrayPrototypeIncludes(
                SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private,
                u,
              ),
          ) !== undefined
        ) {
          throw new DOMException("Invalid key usages", "SyntaxError");
        }
      } else if (
        ArrayPrototypeFind(
          keyUsages,
          (u) =>
            !ArrayPrototypeIncludes(
              SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public,
              u,
            ),
        ) !== undefined
      ) {
        throw new DOMException("Invalid key usages", "SyntaxError");
      }

      // 3.
      if (StringPrototypeToUpperCase(jwk.kty) !== "RSA") {
        throw new DOMException(
          "'kty' property of JsonWebKey must be 'RSA'",
          "DataError",
        );
      }

      // 4.
      if (
        keyUsages.length > 0 && jwk.use !== undefined &&
        StringPrototypeToLowerCase(jwk.use) !==
          SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse
      ) {
        throw new DOMException(
          `'use' property of JsonWebKey must be '${
            SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse
          }'`,
          "DataError",
        );
      }

      // 5.
      if (jwk.key_ops !== undefined) {
        if (
          ArrayPrototypeFind(
            jwk.key_ops,
            (u) => !ArrayPrototypeIncludes(recognisedUsages, u),
          ) !== undefined
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }

        if (
          !ArrayPrototypeEvery(
            jwk.key_ops,
            (u) => ArrayPrototypeIncludes(keyUsages, u),
          )
        ) {
          throw new DOMException(
            "'key_ops' property of JsonWebKey is invalid",
            "DataError",
          );
        }
      }

      if (jwk.ext === false && extractable === true) {
        throw new DOMException(
          "'ext' property of JsonWebKey must not be false if extractable is true",
          "DataError",
        );
      }

      // 7.
      let hash;

      // 8.
      if (normalizedAlgorithm.name === "RSASSA-PKCS1-v1_5") {
        switch (jwk.alg) {
          case undefined:
            hash = undefined;
            break;
          case "RS1":
            hash = "SHA-1";
            break;
          case "RS256":
            hash = "SHA-256";
            break;
          case "RS384":
            hash = "SHA-384";
            break;
          case "RS512":
            hash = "SHA-512";
            break;
          default:
            throw new DOMException(
              `'alg' property of JsonWebKey must be one of 'RS1', 'RS256', 'RS384', 'RS512'`,
              "DataError",
            );
        }
      } else if (normalizedAlgorithm.name === "RSA-PSS") {
        switch (jwk.alg) {
          case undefined:
            hash = undefined;
            break;
          case "PS1":
            hash = "SHA-1";
            break;
          case "PS256":
            hash = "SHA-256";
            break;
          case "PS384":
            hash = "SHA-384";
            break;
          case "PS512":
            hash = "SHA-512";
            break;
          default:
            throw new DOMException(
              `'alg' property of JsonWebKey must be one of 'PS1', 'PS256', 'PS384', 'PS512'`,
              "DataError",
            );
        }
      } else {
        switch (jwk.alg) {
          case undefined:
            hash = undefined;
            break;
          case "RSA-OAEP":
            hash = "SHA-1";
            break;
          case "RSA-OAEP-256":
            hash = "SHA-256";
            break;
          case "RSA-OAEP-384":
            hash = "SHA-384";
            break;
          case "RSA-OAEP-512":
            hash = "SHA-512";
            break;
          default:
            throw new DOMException(
              `'alg' property of JsonWebKey must be one of 'RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', or 'RSA-OAEP-512'`,
              "DataError",
            );
        }
      }

      // 9.
      if (hash !== undefined) {
        // 9.1.
        const normalizedHash = normalizeAlgorithm(hash, "digest");

        // 9.2.
        if (normalizedHash.name !== normalizedAlgorithm.hash.name) {
          throw new DOMException(
            `'alg' property of JsonWebKey must be '${normalizedAlgorithm.name}'`,
            "DataError",
          );
        }
      }

      // 10.
      if (jwk.d !== undefined) {
        // Private key
        const optimizationsPresent = jwk.p !== undefined ||
          jwk.q !== undefined || jwk.dp !== undefined ||
          jwk.dq !== undefined || jwk.qi !== undefined;
        if (optimizationsPresent) {
          if (jwk.q === undefined) {
            throw new DOMException(
              "'q' property of JsonWebKey is required for private keys",
              "DataError",
            );
          }
          if (jwk.dp === undefined) {
            throw new DOMException(
              "'dp' property of JsonWebKey is required for private keys",
              "DataError",
            );
          }
          if (jwk.dq === undefined) {
            throw new DOMException(
              "'dq' property of JsonWebKey is required for private keys",
              "DataError",
            );
          }
          if (jwk.qi === undefined) {
            throw new DOMException(
              "'qi' property of JsonWebKey is required for private keys",
              "DataError",
            );
          }
          if (jwk.oth !== undefined) {
            throw new DOMException(
              "'oth' property of JsonWebKey is not supported",
              "NotSupportedError",
            );
          }
        } else {
          throw new DOMException(
            "only optimized private keys are supported",
            "NotSupportedError",
          );
        }

        const { modulusLength, publicExponent, rawData } = ops
          .op_crypto_import_key(
            {
              algorithm: normalizedAlgorithm.name,
              hash: normalizedAlgorithm.hash.name,
            },
            { jwkPrivateRsa: jwk },
          );

        const handle = {};
        WeakMapPrototypeSet(KEY_STORE, handle, rawData);

        const algorithm = {
          name: normalizedAlgorithm.name,
          modulusLength,
          publicExponent,
          hash: normalizedAlgorithm.hash,
        };

        const key = constructKey(
          "private",
          extractable,
          usageIntersection(keyUsages, recognisedUsages),
          algorithm,
          handle,
        );

        return key;
      } else {
        // Validate that this is a valid public key.
        if (jwk.n === undefined) {
          throw new DOMException(
            "'n' property of JsonWebKey is required for public keys",
            "DataError",
          );
        }
        if (jwk.e === undefined) {
          throw new DOMException(
            "'e' property of JsonWebKey is required for public keys",
            "DataError",
          );
        }

        const { modulusLength, publicExponent, rawData } = ops
          .op_crypto_import_key(
            {
              algorithm: normalizedAlgorithm.name,
              hash: normalizedAlgorithm.hash.name,
            },
            { jwkPublicRsa: jwk },
          );

        const handle = {};
        WeakMapPrototypeSet(KEY_STORE, handle, rawData);

        const algorithm = {
          name: normalizedAlgorithm.name,
          modulusLength,
          publicExponent,
          hash: normalizedAlgorithm.hash,
        };

        const key = constructKey(
          "public",
          extractable,
          usageIntersection(keyUsages, recognisedUsages),
          algorithm,
          handle,
        );

        return key;
      }
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

function importKeyHKDF(
  format,
  keyData,
  extractable,
  keyUsages,
) {
  if (format !== "raw") {
    throw new DOMException("Format not supported", "NotSupportedError");
  }

  // 1.
  if (
    ArrayPrototypeFind(
      keyUsages,
      (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
    ) !== undefined
  ) {
    throw new DOMException("Invalid key usages", "SyntaxError");
  }

  // 2.
  if (extractable !== false) {
    throw new DOMException(
      "Key must not be extractable",
      "SyntaxError",
    );
  }

  // 3.
  const handle = {};
  WeakMapPrototypeSet(KEY_STORE, handle, {
    type: "secret",
    data: keyData,
  });

  // 4-8.
  const algorithm = {
    name: "HKDF",
  };
  const key = constructKey(
    "secret",
    false,
    usageIntersection(keyUsages, recognisedUsages),
    algorithm,
    handle,
  );

  // 9.
  return key;
}

function importKeyPBKDF2(
  format,
  keyData,
  extractable,
  keyUsages,
) {
  // 1.
  if (format !== "raw") {
    throw new DOMException("Format not supported", "NotSupportedError");
  }

  // 2.
  if (
    ArrayPrototypeFind(
      keyUsages,
      (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
    ) !== undefined
  ) {
    throw new DOMException("Invalid key usages", "SyntaxError");
  }

  // 3.
  if (extractable !== false) {
    throw new DOMException(
      "Key must not be extractable",
      "SyntaxError",
    );
  }

  // 4.
  const handle = {};
  WeakMapPrototypeSet(KEY_STORE, handle, {
    type: "secret",
    data: keyData,
  });

  // 5-9.
  const algorithm = {
    name: "PBKDF2",
  };
  const key = constructKey(
    "secret",
    false,
    usageIntersection(keyUsages, recognisedUsages),
    algorithm,
    handle,
  );

  // 10.
  return key;
}

function exportKeyHMAC(format, key, innerKey) {
  // 1.
  if (innerKey == null) {
    throw new DOMException("Key is not available", "OperationError");
  }

  switch (format) {
    // 3.
    case "raw": {
      const bits = innerKey.data;
      for (let _i = 7 & (8 - bits.length % 8); _i > 0; _i--) {
        bits.push(0);
      }
      // 4-5.
      return bits.buffer;
    }
    case "jwk": {
      // 1-2.
      const jwk = {
        kty: "oct",
      };

      // 3.
      const data = ops.op_crypto_export_key({
        format: "jwksecret",
        algorithm: key[_algorithm].name,
      }, innerKey);
      jwk.k = data.k;

      // 4.
      const algorithm = key[_algorithm];
      // 5.
      const hash = algorithm.hash;
      // 6.
      switch (hash.name) {
        case "SHA-1":
          jwk.alg = "HS1";
          break;
        case "SHA-256":
          jwk.alg = "HS256";
          break;
        case "SHA-384":
          jwk.alg = "HS384";
          break;
        case "SHA-512":
          jwk.alg = "HS512";
          break;
        default:
          throw new DOMException(
            "Hash algorithm not supported",
            "NotSupportedError",
          );
      }
      // 7.
      jwk.key_ops = key.usages;
      // 8.
      jwk.ext = key[_extractable];
      // 9.
      return jwk;
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

function exportKeyRSA(format, key, innerKey) {
  switch (format) {
    case "pkcs8": {
      // 1.
      if (key[_type] !== "private") {
        throw new DOMException(
          "Key is not a private key",
          "InvalidAccessError",
        );
      }

      // 2.
      const data = ops.op_crypto_export_key({
        algorithm: key[_algorithm].name,
        format: "pkcs8",
      }, innerKey);

      // 3.
      return data.buffer;
    }
    case "spki": {
      // 1.
      if (key[_type] !== "public") {
        throw new DOMException(
          "Key is not a public key",
          "InvalidAccessError",
        );
      }

      // 2.
      const data = ops.op_crypto_export_key({
        algorithm: key[_algorithm].name,
        format: "spki",
      }, innerKey);

      // 3.
      return data.buffer;
    }
    case "jwk": {
      // 1-2.
      const jwk = {
        kty: "RSA",
      };

      // 3.
      const hash = key[_algorithm].hash.name;

      // 4.
      if (key[_algorithm].name === "RSASSA-PKCS1-v1_5") {
        switch (hash) {
          case "SHA-1":
            jwk.alg = "RS1";
            break;
          case "SHA-256":
            jwk.alg = "RS256";
            break;
          case "SHA-384":
            jwk.alg = "RS384";
            break;
          case "SHA-512":
            jwk.alg = "RS512";
            break;
          default:
            throw new DOMException(
              "Hash algorithm not supported",
              "NotSupportedError",
            );
        }
      } else if (key[_algorithm].name === "RSA-PSS") {
        switch (hash) {
          case "SHA-1":
            jwk.alg = "PS1";
            break;
          case "SHA-256":
            jwk.alg = "PS256";
            break;
          case "SHA-384":
            jwk.alg = "PS384";
            break;
          case "SHA-512":
            jwk.alg = "PS512";
            break;
          default:
            throw new DOMException(
              "Hash algorithm not supported",
              "NotSupportedError",
            );
        }
      } else {
        switch (hash) {
          case "SHA-1":
            jwk.alg = "RSA-OAEP";
            break;
          case "SHA-256":
            jwk.alg = "RSA-OAEP-256";
            break;
          case "SHA-384":
            jwk.alg = "RSA-OAEP-384";
            break;
          case "SHA-512":
            jwk.alg = "RSA-OAEP-512";
            break;
          default:
            throw new DOMException(
              "Hash algorithm not supported",
              "NotSupportedError",
            );
        }
      }

      // 5-6.
      const data = ops.op_crypto_export_key({
        format: key[_type] === "private" ? "jwkprivate" : "jwkpublic",
        algorithm: key[_algorithm].name,
      }, innerKey);
      ObjectAssign(jwk, data);

      // 7.
      jwk.key_ops = key.usages;

      // 8.
      jwk.ext = key[_extractable];

      return jwk;
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

function exportKeyEd25519(format, key, innerKey) {
  switch (format) {
    case "raw": {
      // 1.
      if (key[_type] !== "public") {
        throw new DOMException(
          "Key is not a public key",
          "InvalidAccessError",
        );
      }

      // 2-3.
      return innerKey.buffer;
    }
    case "spki": {
      // 1.
      if (key[_type] !== "public") {
        throw new DOMException(
          "Key is not a public key",
          "InvalidAccessError",
        );
      }

      const spkiDer = ops.op_export_spki_ed25519(innerKey);
      return spkiDer.buffer;
    }
    case "pkcs8": {
      // 1.
      if (key[_type] !== "private") {
        throw new DOMException(
          "Key is not a public key",
          "InvalidAccessError",
        );
      }

      const pkcs8Der = ops.op_export_pkcs8_ed25519(
        new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]),
      );
      pkcs8Der[15] = 0x20;
      return pkcs8Der.buffer;
    }
    case "jwk": {
      const x = key[_type] === "private"
        ? ops.op_jwk_x_ed25519(innerKey)
        : ops.op_crypto_base64url_encode(innerKey);
      const jwk = {
        kty: "OKP",
        alg: "EdDSA",
        crv: "Ed25519",
        x,
        "key_ops": key.usages,
        ext: key[_extractable],
      };
      if (key[_type] === "private") {
        jwk.d = ops.op_crypto_base64url_encode(innerKey);
      }
      return jwk;
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

function exportKeyX25519(format, key, innerKey) {
  switch (format) {
    case "raw": {
      // 1.
      if (key[_type] !== "public") {
        throw new DOMException(
          "Key is not a public key",
          "InvalidAccessError",
        );
      }

      // 2-3.
      return innerKey.buffer;
    }
    case "spki": {
      // 1.
      if (key[_type] !== "public") {
        throw new DOMException(
          "Key is not a public key",
          "InvalidAccessError",
        );
      }

      const spkiDer = ops.op_export_spki_x25519(innerKey);
      return spkiDer.buffer;
    }
    case "pkcs8": {
      // 1.
      if (key[_type] !== "private") {
        throw new DOMException(
          "Key is not a public key",
          "InvalidAccessError",
        );
      }

      const pkcs8Der = ops.op_export_pkcs8_x25519(
        new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]),
      );
      pkcs8Der[15] = 0x20;
      return pkcs8Der.buffer;
    }
    case "jwk": {
      if (key[_type] === "private") {
        throw new DOMException("Not implemented", "NotSupportedError");
      }
      const x = ops.op_crypto_base64url_encode(innerKey);
      const jwk = {
        kty: "OKP",
        crv: "X25519",
        x,
        "key_ops": key.usages,
        ext: key[_extractable],
      };
      return jwk;
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

function exportKeyEC(format, key, innerKey) {
  switch (format) {
    case "raw": {
      // 1.
      if (key[_type] !== "public") {
        throw new DOMException(
          "Key is not a public key",
          "InvalidAccessError",
        );
      }

      // 2.
      const data = ops.op_crypto_export_key({
        algorithm: key[_algorithm].name,
        namedCurve: key[_algorithm].namedCurve,
        format: "raw",
      }, innerKey);

      return data.buffer;
    }
    case "pkcs8": {
      // 1.
      if (key[_type] !== "private") {
        throw new DOMException(
          "Key is not a private key",
          "InvalidAccessError",
        );
      }

      // 2.
      const data = ops.op_crypto_export_key({
        algorithm: key[_algorithm].name,
        namedCurve: key[_algorithm].namedCurve,
        format: "pkcs8",
      }, innerKey);

      return data.buffer;
    }
    case "spki": {
      // 1.
      if (key[_type] !== "public") {
        throw new DOMException(
          "Key is not a public key",
          "InvalidAccessError",
        );
      }

      // 2.
      const data = ops.op_crypto_export_key({
        algorithm: key[_algorithm].name,
        namedCurve: key[_algorithm].namedCurve,
        format: "spki",
      }, innerKey);

      return data.buffer;
    }
    case "jwk": {
      if (key[_algorithm].name == "ECDSA") {
        // 1-2.
        const jwk = {
          kty: "EC",
        };

        // 3.1
        jwk.crv = key[_algorithm].namedCurve;

        // Missing from spec
        let algNamedCurve;

        switch (key[_algorithm].namedCurve) {
          case "P-256": {
            algNamedCurve = "ES256";
            break;
          }
          case "P-384": {
            algNamedCurve = "ES384";
            break;
          }
          case "P-521": {
            algNamedCurve = "ES512";
            break;
          }
          default:
            throw new DOMException(
              "Curve algorithm not supported",
              "DataError",
            );
        }

        jwk.alg = algNamedCurve;

        // 3.2 - 3.4.
        const data = ops.op_crypto_export_key({
          format: key[_type] === "private" ? "jwkprivate" : "jwkpublic",
          algorithm: key[_algorithm].name,
          namedCurve: key[_algorithm].namedCurve,
        }, innerKey);
        ObjectAssign(jwk, data);

        // 4.
        jwk.key_ops = key.usages;

        // 5.
        jwk.ext = key[_extractable];

        return jwk;
      } else { // ECDH
        // 1-2.
        const jwk = {
          kty: "EC",
        };

        // missing step from spec
        jwk.alg = "ECDH";

        // 3.1
        jwk.crv = key[_algorithm].namedCurve;

        // 3.2 - 3.4
        const data = ops.op_crypto_export_key({
          format: key[_type] === "private" ? "jwkprivate" : "jwkpublic",
          algorithm: key[_algorithm].name,
          namedCurve: key[_algorithm].namedCurve,
        }, innerKey);
        ObjectAssign(jwk, data);

        // 4.
        jwk.key_ops = key.usages;

        // 5.
        jwk.ext = key[_extractable];

        return jwk;
      }
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

async function generateKeyAES(normalizedAlgorithm, extractable, usages) {
  const algorithmName = normalizedAlgorithm.name;

  // 2.
  if (!ArrayPrototypeIncludes([128, 192, 256], normalizedAlgorithm.length)) {
    throw new DOMException("Invalid key length", "OperationError");
  }

  // 3.
  const keyData = await core.opAsync("op_crypto_generate_key", {
    algorithm: "AES",
    length: normalizedAlgorithm.length,
  });
  const handle = {};
  WeakMapPrototypeSet(KEY_STORE, handle, {
    type: "secret",
    data: keyData,
  });

  // 6-8.
  const algorithm = {
    name: algorithmName,
    length: normalizedAlgorithm.length,
  };

  // 9-11.
  const key = constructKey(
    "secret",
    extractable,
    usages,
    algorithm,
    handle,
  );

  // 12.
  return key;
}

async function deriveBits(normalizedAlgorithm, baseKey, length) {
  switch (normalizedAlgorithm.name) {
    case "PBKDF2": {
      // 1.
      if (length == null || length == 0 || length % 8 !== 0) {
        throw new DOMException("Invalid length", "OperationError");
      }

      if (normalizedAlgorithm.iterations == 0) {
        throw new DOMException(
          "iterations must not be zero",
          "OperationError",
        );
      }

      const handle = baseKey[_handle];
      const keyData = WeakMapPrototypeGet(KEY_STORE, handle);

      normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt);

      const buf = await core.opAsync("op_crypto_derive_bits", {
        key: keyData,
        algorithm: "PBKDF2",
        hash: normalizedAlgorithm.hash.name,
        iterations: normalizedAlgorithm.iterations,
        length,
      }, normalizedAlgorithm.salt);

      return buf.buffer;
    }
    case "ECDH": {
      // 1.
      if (baseKey[_type] !== "private") {
        throw new DOMException("Invalid key type", "InvalidAccessError");
      }
      // 2.
      const publicKey = normalizedAlgorithm.public;
      // 3.
      if (publicKey[_type] !== "public") {
        throw new DOMException("Invalid key type", "InvalidAccessError");
      }
      // 4.
      if (publicKey[_algorithm].name !== baseKey[_algorithm].name) {
        throw new DOMException(
          "Algorithm mismatch",
          "InvalidAccessError",
        );
      }
      // 5.
      if (
        publicKey[_algorithm].namedCurve !== baseKey[_algorithm].namedCurve
      ) {
        throw new DOMException(
          "namedCurve mismatch",
          "InvalidAccessError",
        );
      }
      // 6.
      if (
        ArrayPrototypeIncludes(
          supportedNamedCurves,
          publicKey[_algorithm].namedCurve,
        )
      ) {
        const baseKeyhandle = baseKey[_handle];
        const baseKeyData = WeakMapPrototypeGet(KEY_STORE, baseKeyhandle);
        const publicKeyhandle = publicKey[_handle];
        const publicKeyData = WeakMapPrototypeGet(KEY_STORE, publicKeyhandle);

        const buf = await core.opAsync("op_crypto_derive_bits", {
          key: baseKeyData,
          publicKey: publicKeyData,
          algorithm: "ECDH",
          namedCurve: publicKey[_algorithm].namedCurve,
          length,
        });

        // 8.
        if (length === null) {
          return buf.buffer;
        } else if (buf.buffer.byteLength * 8 < length) {
          throw new DOMException("Invalid length", "OperationError");
        } else {
          return buf.buffer.slice(0, MathCeil(length / 8));
        }
      } else {
        throw new DOMException("Not implemented", "NotSupportedError");
      }
    }
    case "HKDF": {
      // 1.
      if (length === null || length === 0 || length % 8 !== 0) {
        throw new DOMException("Invalid length", "OperationError");
      }

      const handle = baseKey[_handle];
      const keyDerivationKey = WeakMapPrototypeGet(KEY_STORE, handle);

      normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt);

      normalizedAlgorithm.info = copyBuffer(normalizedAlgorithm.info);

      const buf = await core.opAsync("op_crypto_derive_bits", {
        key: keyDerivationKey,
        algorithm: "HKDF",
        hash: normalizedAlgorithm.hash.name,
        info: normalizedAlgorithm.info,
        length,
      }, normalizedAlgorithm.salt);

      return buf.buffer;
    }
    case "X25519": {
      // 1.
      if (baseKey[_type] !== "private") {
        throw new DOMException("Invalid key type", "InvalidAccessError");
      }
      // 2.
      const publicKey = normalizedAlgorithm.public;
      // 3.
      if (publicKey[_type] !== "public") {
        throw new DOMException("Invalid key type", "InvalidAccessError");
      }
      // 4.
      if (publicKey[_algorithm].name !== baseKey[_algorithm].name) {
        throw new DOMException(
          "Algorithm mismatch",
          "InvalidAccessError",
        );
      }

      // 5.
      const kHandle = baseKey[_handle];
      const k = WeakMapPrototypeGet(KEY_STORE, kHandle);

      const uHandle = publicKey[_handle];
      const u = WeakMapPrototypeGet(KEY_STORE, uHandle);

      const secret = new Uint8Array(32);
      const isIdentity = ops.op_derive_bits_x25519(k, u, secret);

      // 6.
      if (isIdentity) {
        throw new DOMException("Invalid key", "OperationError");
      }

      // 7.
      if (length === null) {
        return secret.buffer;
      } else if (
        secret.buffer.byteLength * 8 < length
      ) {
        throw new DOMException("Invalid length", "OperationError");
      } else {
        return secret.buffer.slice(0, MathCeil(length / 8));
      }
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

async function encrypt(normalizedAlgorithm, key, data) {
  const handle = key[_handle];
  const keyData = WeakMapPrototypeGet(KEY_STORE, handle);

  switch (normalizedAlgorithm.name) {
    case "RSA-OAEP": {
      // 1.
      if (key[_type] !== "public") {
        throw new DOMException(
          "Key type not supported",
          "InvalidAccessError",
        );
      }

      // 2.
      if (normalizedAlgorithm.label) {
        normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label);
      } else {
        normalizedAlgorithm.label = new Uint8Array();
      }

      // 3-5.
      const hashAlgorithm = key[_algorithm].hash.name;
      const cipherText = await core.opAsync("op_crypto_encrypt", {
        key: keyData,
        algorithm: "RSA-OAEP",
        hash: hashAlgorithm,
        label: normalizedAlgorithm.label,
      }, data);

      // 6.
      return cipherText.buffer;
    }
    case "AES-CBC": {
      normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);

      // 1.
      if (normalizedAlgorithm.iv.byteLength !== 16) {
        throw new DOMException(
          "Initialization vector must be 16 bytes",
          "OperationError",
        );
      }

      // 2.
      const cipherText = await core.opAsync("op_crypto_encrypt", {
        key: keyData,
        algorithm: "AES-CBC",
        length: key[_algorithm].length,
        iv: normalizedAlgorithm.iv,
      }, data);

      // 4.
      return cipherText.buffer;
    }
    case "AES-CTR": {
      normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter);

      // 1.
      if (normalizedAlgorithm.counter.byteLength !== 16) {
        throw new DOMException(
          "Counter vector must be 16 bytes",
          "OperationError",
        );
      }

      // 2.
      if (
        normalizedAlgorithm.length == 0 || normalizedAlgorithm.length > 128
      ) {
        throw new DOMException(
          "Counter length must not be 0 or greater than 128",
          "OperationError",
        );
      }

      // 3.
      const cipherText = await core.opAsync("op_crypto_encrypt", {
        key: keyData,
        algorithm: "AES-CTR",
        keyLength: key[_algorithm].length,
        counter: normalizedAlgorithm.counter,
        ctrLength: normalizedAlgorithm.length,
      }, data);

      // 4.
      return cipherText.buffer;
    }
    case "AES-GCM": {
      normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);

      // 1.
      if (data.byteLength > (2 ** 39) - 256) {
        throw new DOMException(
          "Plaintext too large",
          "OperationError",
        );
      }

      // 2.
      // We only support 96-bit and 128-bit nonce.
      if (
        ArrayPrototypeIncludes(
          [12, 16],
          normalizedAlgorithm.iv.byteLength,
        ) === undefined
      ) {
        throw new DOMException(
          "Initialization vector length not supported",
          "NotSupportedError",
        );
      }

      // 3.
      if (normalizedAlgorithm.additionalData !== undefined) {
        if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) {
          throw new DOMException(
            "Additional data too large",
            "OperationError",
          );
        }
      }

      // 4.
      if (normalizedAlgorithm.tagLength == undefined) {
        normalizedAlgorithm.tagLength = 128;
      } else if (
        !ArrayPrototypeIncludes(
          [32, 64, 96, 104, 112, 120, 128],
          normalizedAlgorithm.tagLength,
        )
      ) {
        throw new DOMException(
          "Invalid tag length",
          "OperationError",
        );
      }
      // 5.
      if (normalizedAlgorithm.additionalData) {
        normalizedAlgorithm.additionalData = copyBuffer(
          normalizedAlgorithm.additionalData,
        );
      }
      // 6-7.
      const cipherText = await core.opAsync("op_crypto_encrypt", {
        key: keyData,
        algorithm: "AES-GCM",
        length: key[_algorithm].length,
        iv: normalizedAlgorithm.iv,
        additionalData: normalizedAlgorithm.additionalData || null,
        tagLength: normalizedAlgorithm.tagLength,
      }, data);

      // 8.
      return cipherText.buffer;
    }
    default:
      throw new DOMException("Not implemented", "NotSupportedError");
  }
}

webidl.configurePrototype(SubtleCrypto);
const subtle = webidl.createBranded(SubtleCrypto);

class Crypto {
  constructor() {
    webidl.illegalConstructor();
  }

  getRandomValues(arrayBufferView) {
    webidl.assertBranded(this, CryptoPrototype);
    const prefix = "Failed to execute 'getRandomValues' on 'Crypto'";
    webidl.requiredArguments(arguments.length, 1, { prefix });
    // Fast path for Uint8Array
    if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView)) {
      ops.op_crypto_get_random_values(arrayBufferView);
      return arrayBufferView;
    }
    arrayBufferView = webidl.converters.ArrayBufferView(arrayBufferView, {
      prefix,
      context: "Argument 1",
    });
    if (
      !(
        ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, arrayBufferView) ||
        ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView) ||
        ObjectPrototypeIsPrototypeOf(
          Uint8ClampedArrayPrototype,
          arrayBufferView,
        ) ||
        ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, arrayBufferView) ||
        ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, arrayBufferView) ||
        ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, arrayBufferView) ||
        ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, arrayBufferView) ||
        ObjectPrototypeIsPrototypeOf(
          BigInt64ArrayPrototype,
          arrayBufferView,
        ) ||
        ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, arrayBufferView)
      )
    ) {
      throw new DOMException(
        "The provided ArrayBufferView is not an integer array type",
        "TypeMismatchError",
      );
    }
    const ui8 = new Uint8Array(
      arrayBufferView.buffer,
      arrayBufferView.byteOffset,
      arrayBufferView.byteLength,
    );
    ops.op_crypto_get_random_values(ui8);
    return arrayBufferView;
  }

  randomUUID() {
    webidl.assertBranded(this, CryptoPrototype);
    return ops.op_crypto_random_uuid();
  }

  get subtle() {
    webidl.assertBranded(this, CryptoPrototype);
    return subtle;
  }

  [SymbolFor("Deno.customInspect")](inspect) {
    return `${this.constructor.name} ${inspect({})}`;
  }
}

webidl.configurePrototype(Crypto);
const CryptoPrototype = Crypto.prototype;

const crypto = webidl.createBranded(Crypto);
export { Crypto, crypto, CryptoKey, SubtleCrypto };