@ 261106.1
current
'use strict';

const {
  ArrayBufferIsView,
  ArrayBufferPrototypeGetByteLength,
  ArrayBufferPrototypeGetDetached,
  ArrayPrototypeIncludes,
  ArrayPrototypePush,
  ArrayPrototypeSlice,
  BigInt,
  DataViewPrototypeGetBuffer,
  DataViewPrototypeGetByteLength,
  DataViewPrototypeGetByteOffset,
  MathFloor,
  MathMin,
  Number,
  ObjectDefineProperty,
  ObjectEntries,
  ObjectKeys,
  ObjectPrototypeHasOwnProperty,
  ObjectSetPrototypeOf,
  PromisePrototypeThen,
  PromiseReject,
  PromiseWithResolvers,
  SafeMap,
  SafeSet,
  StringPrototypeCharCodeAt,
  StringPrototypeStartsWith,
  StringPrototypeToUpperCase,
  Symbol,
  TypedArrayPrototypeGetBuffer,
  TypedArrayPrototypeGetByteLength,
  TypedArrayPrototypeGetByteOffset,
  TypedArrayPrototypeGetLength,
  Uint8Array,
} = primordials;

const {
  getCiphers: _getCiphers,
  getCurves: _getCurves,
  getHashes: _getHashes,
  getMacs: _getMacs,
  secureHeapUsed: _secureHeapUsed,
  getCachedAliases,
  getCachedMacAliases,
  getOpenSSLSecLevelCrypto: getOpenSSLSecLevel,
  kKeyVariantAES_OCB_128: hasAesOcbMode,
  Argon2Job,
  getFipsCrypto,
  getFipsCryptoGeneration,
  KmacJob,
  isKeyAlgorithmAvailable,
} = internalBinding('crypto');

const { openssl_is_boringssl: isBoringSSL } = process.features;

let fips;
let fipsGeneration;
function isFips() {
  const generation = getFipsCryptoGeneration();
  if (fipsGeneration !== generation) {
    fips = getFipsCrypto() === 1;
    fipsGeneration = generation;
  }
  return fips;
}

const { getOptionValue } = require('internal/options');

const normalizeHashName = require('internal/crypto/hashnames');

const {
  codes: {
    ERR_INVALID_ARG_TYPE,
  },
  hideStackFrames,
} = require('internal/errors');

const {
  validateArray,
  validateString,
} = require('internal/validators');

const { Buffer } = require('buffer');

const {
  emitExperimentalWarning,
  filterDuplicateStrings,
  lazyDOMException,
  setOwnProperty,
} = require('internal/util');

const {
  namespace: {
    isBuildingSnapshot,
    addSerializeCallback,
  },
} = require('internal/v8/startup_snapshot');

const {
  isDataView,
  isArrayBuffer,
  isArrayBufferView,
  isAnyArrayBuffer,
  isPromise,
} = require('internal/util/types');

const kHandle = Symbol('kHandle');

// This is here because many functions accepted binary strings without
// any explicit encoding in older versions of node, and we don't want
// to break them unnecessarily.
function toBuf(val, encoding) {
  if (typeof val === 'string') {
    if (encoding === 'buffer')
      encoding = 'utf8';
    return Buffer.from(val, encoding);
  }
  return val;
}

let _hashCache;
let _macCache;
if (isBuildingSnapshot()) {
  addSerializeCallback(() => {
    fips = undefined;
    fipsGeneration = undefined;
    supportedAlgorithmsGeneration = undefined;
    _hashCache = undefined;
    _macCache = undefined;
  });
}

function getHashCache() {
  while (_hashCache === undefined) {
    const generation = getFipsCryptoGeneration();
    const cache = getCachedAliases();
    if (generation !== getFipsCryptoGeneration()) continue;
    _hashCache = cache;
  }
  return _hashCache;
}

function getMacCache() {
  while (_macCache === undefined) {
    const generation = getFipsCryptoGeneration();
    const cache = getCachedMacAliases();
    if (generation !== getFipsCryptoGeneration()) continue;
    _macCache = cache;
  }
  return _macCache;
}

function cachedArrayByFipsGeneration(fn, onRefresh) {
  let result;
  let generation;
  if (isBuildingSnapshot()) {
    addSerializeCallback(() => {
      result = undefined;
      generation = undefined;
    });
  }

  return () => {
    while (true) {
      const current = getFipsCryptoGeneration();
      if (result === undefined || generation !== current) {
        const next = fn();
        if (current !== getFipsCryptoGeneration()) continue;
        result = next;
        generation = current;
        if (onRefresh !== undefined) onRefresh();
      }
      return ArrayPrototypeSlice(result);
    }
  };
}

function getCachedHashId(algorithm) {
  const result = getHashCache()[algorithm];
  return result === undefined ? -1 : result;
}

function getCachedMacId(algorithm) {
  const result = getMacCache()[algorithm];
  return result === undefined ? -1 : result;
}

const getCiphers = cachedArrayByFipsGeneration(
  () => filterDuplicateStrings(_getCiphers()));
const getHashes = cachedArrayByFipsGeneration(
  () => filterDuplicateStrings(_getHashes()),
  () => {
    _hashCache = undefined;
  });
const getMacs = cachedArrayByFipsGeneration(
  () => filterDuplicateStrings(_getMacs(), true),
  () => {
    _macCache = undefined;
  });

const getCurves = cachedArrayByFipsGeneration(
  () => filterDuplicateStrings(_getCurves()));

const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => {
  if (isAnyArrayBuffer(buffer))
    return buffer;
  if (typeof buffer === 'string') {
    if (encoding === 'buffer')
      encoding = 'utf8';
    return Buffer.from(buffer, encoding);
  }
  if (!isArrayBufferView(buffer)) {
    throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
      name,
      [
        'string',
        'ArrayBuffer',
        'Buffer',
        'TypedArray',
        'DataView',
      ],
      buffer,
    );
  }
  return buffer;
});

// The maximum buffer size that we'll support in the WebCrypto impl
const kMaxBufferLength = (2 ** 31) - 1;

// The EC named curves that we currently support via the Web Crypto API.
const kNamedCurveAliases = {
  'P-256': 'prime256v1',
  'P-384': 'secp384r1',
  'P-521': 'secp521r1',
};

// Algorithm definitions organized by algorithm name
const kAlgorithmDefinitions = {
  'AES-CBC': {
    'generateKey': 'AesKeyGenParams',
    'exportKey': null,
    'importKey': null,
    'encrypt': 'AesCbcParams',
    'decrypt': 'AesCbcParams',
    'get key length': 'AesDerivedKeyParams',
  },
  'AES-CTR': {
    'generateKey': 'AesKeyGenParams',
    'exportKey': null,
    'importKey': null,
    'encrypt': 'AesCtrParams',
    'decrypt': 'AesCtrParams',
    'get key length': 'AesDerivedKeyParams',
  },
  'AES-GCM': {
    'generateKey': 'AesKeyGenParams',
    'exportKey': null,
    'importKey': null,
    'encrypt': 'AeadParams',
    'decrypt': 'AeadParams',
    'get key length': 'AesDerivedKeyParams',
  },
  'AES-KW': {
    'generateKey': 'AesKeyGenParams',
    'exportKey': null,
    'importKey': null,
    'get key length': 'AesDerivedKeyParams',
    'wrapKey': null,
    'unwrapKey': null,
  },
  'AES-OCB': {
    'generateKey': 'AesKeyGenParams',
    'exportKey': null,
    'importKey': null,
    'encrypt': 'AeadParams',
    'decrypt': 'AeadParams',
    'get key length': 'AesDerivedKeyParams',
  },
  'Argon2d': {
    'deriveBits': 'Argon2Params',
    'get key length': null,
    'importKey': null,
  },
  'Argon2i': {
    'deriveBits': 'Argon2Params',
    'get key length': null,
    'importKey': null,
  },
  'Argon2id': {
    'deriveBits': 'Argon2Params',
    'get key length': null,
    'importKey': null,
  },
  'ChaCha20-Poly1305': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'encrypt': 'AeadParams',
    'decrypt': 'AeadParams',
    'get key length': null,
  },
  'cSHAKE128': { 'digest': 'CShakeParams' },
  'cSHAKE256': { 'digest': 'CShakeParams' },
  'KT128': { 'digest': 'KangarooTwelveParams' },
  'KT256': { 'digest': 'KangarooTwelveParams' },
  'TurboSHAKE128': { 'digest': 'TurboShakeParams' },
  'TurboSHAKE256': { 'digest': 'TurboShakeParams' },
  'ECDH': {
    'generateKey': 'EcKeyGenParams',
    'exportKey': null,
    'importKey': 'EcKeyImportParams',
    'deriveBits': 'EcdhKeyDeriveParams',
  },
  'ECDSA': {
    'generateKey': 'EcKeyGenParams',
    'exportKey': null,
    'importKey': 'EcKeyImportParams',
    'sign': 'EcdsaParams',
    'verify': 'EcdsaParams',
  },
  'Ed25519': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'sign': null,
    'verify': null,
  },
  'Ed448': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'sign': 'ContextParams',
    'verify': 'ContextParams',
  },
  'HKDF': {
    'importKey': null,
    'deriveBits': 'HkdfParams',
    'get key length': null,
  },
  'HMAC': {
    'generateKey': 'HmacKeyGenParams',
    'exportKey': null,
    'importKey': 'HmacImportParams',
    'sign': null,
    'verify': null,
    'get key length': 'HmacImportParams',
  },
  'KMAC128': {
    'generateKey': 'KmacKeyGenParams',
    'exportKey': null,
    'importKey': 'KmacImportParams',
    'sign': 'KmacParams',
    'verify': 'KmacParams',
    'get key length': 'KmacImportParams',
  },
  'KMAC256': {
    'generateKey': 'KmacKeyGenParams',
    'exportKey': null,
    'importKey': 'KmacImportParams',
    'sign': 'KmacParams',
    'verify': 'KmacParams',
    'get key length': 'KmacImportParams',
  },
  'ML-DSA-44': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'sign': 'ContextParams',
    'verify': 'ContextParams',
  },
  'ML-DSA-65': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'sign': 'ContextParams',
    'verify': 'ContextParams',
  },
  'ML-DSA-87': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'sign': 'ContextParams',
    'verify': 'ContextParams',
  },
  'ML-KEM-512': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'encapsulate': null,
    'decapsulate': null,
    'get shared key length': null,
  },
  'ML-KEM-768': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'encapsulate': null,
    'decapsulate': null,
    'get shared key length': null,
  },
  'ML-KEM-1024': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'encapsulate': null,
    'decapsulate': null,
    'get shared key length': null,
  },
  'MLKEM768-P256': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'encapsulate': null,
    'decapsulate': null,
    'get shared key length': null,
  },
  'MLKEM768-X25519': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'encapsulate': null,
    'decapsulate': null,
    'get shared key length': null,
  },
  'MLKEM1024-P384': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'encapsulate': null,
    'decapsulate': null,
    'get shared key length': null,
  },
  'PBKDF2': {
    'importKey': null,
    'deriveBits': 'Pbkdf2Params',
    'get key length': null,
  },
  'RSA-OAEP': {
    'generateKey': 'RsaHashedKeyGenParams',
    'exportKey': null,
    'importKey': 'RsaHashedImportParams',
    'encrypt': 'RsaOaepParams',
    'decrypt': 'RsaOaepParams',
  },
  'RSA-PSS': {
    'generateKey': 'RsaHashedKeyGenParams',
    'exportKey': null,
    'importKey': 'RsaHashedImportParams',
    'sign': 'RsaPssParams',
    'verify': 'RsaPssParams',
  },
  'RSASSA-PKCS1-v1_5': {
    'generateKey': 'RsaHashedKeyGenParams',
    'exportKey': null,
    'importKey': 'RsaHashedImportParams',
    'sign': null,
    'verify': null,
  },
  'SHA-1': { 'digest': null },
  'SHA-256': { 'digest': null },
  'SHA-384': { 'digest': null },
  'SHA-512': { 'digest': null },
  'SHA3-256': { 'digest': null },
  'SHA3-384': { 'digest': null },
  'SHA3-512': { 'digest': null },
  'X25519': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'deriveBits': 'EcdhKeyDeriveParams',
  },
  'X448': {
    'generateKey': null,
    'exportKey': null,
    'importKey': null,
    'deriveBits': 'EcdhKeyDeriveParams',
  },
};

function has(algorithms, name) {
  return ArrayPrototypeIncludes(algorithms, name);
}

// Re-evaluated when the active FIPS state changes.
function getConditionalAlgorithms() {
  const mlKem768 = isKeyAlgorithmAvailable('ML-KEM-768');
  const mlKem1024 = isKeyAlgorithmAvailable('ML-KEM-1024');
  const ciphers = getCiphers();
  const hashes = getHashes();
  const macs = getMacs();

  const fips = isFips();

  return {
    'AES-OCB': !!hasAesOcbMode &&
      (has(ciphers, 'aes-128-ocb') ||
       has(ciphers, 'aes-192-ocb') ||
       has(ciphers, 'aes-256-ocb')),
    'Argon2d': !!Argon2Job && !fips,
    'Argon2i': !!Argon2Job && !fips,
    'Argon2id': !!Argon2Job && !fips,
    'ChaCha20-Poly1305': isBoringSSL ||
      has(ciphers, 'chacha20-poly1305'),
    'cSHAKE128': has(hashes, 'shake128'),
    'cSHAKE256': has(hashes, 'shake256'),
    'Ed25519': isKeyAlgorithmAvailable('Ed25519'),
    'Ed448': isKeyAlgorithmAvailable('Ed448'),
    'KMAC128': !!KmacJob && has(macs, 'kmac128'),
    'KMAC256': !!KmacJob && has(macs, 'kmac256'),
    'KT128': !fips,
    'KT256': !fips,
    'ML-DSA-44': isKeyAlgorithmAvailable('ML-DSA-44'),
    'ML-DSA-65': isKeyAlgorithmAvailable('ML-DSA-65'),
    'ML-DSA-87': isKeyAlgorithmAvailable('ML-DSA-87'),
    'ML-KEM-512': isKeyAlgorithmAvailable('ML-KEM-512'),
    'ML-KEM-768': mlKem768,
    'ML-KEM-1024': mlKem1024,
    'MLKEM768-P256': !fips && mlKem768 &&
      (!isBoringSSL ||
        (has(hashes, 'sha3-256') &&
         has(hashes, 'shake256'))),
    'MLKEM768-X25519': !fips && mlKem768 &&
      (!isBoringSSL ||
        (has(hashes, 'sha3-256') &&
         has(hashes, 'shake256'))),
    'MLKEM1024-P384': !fips && mlKem1024 &&
      (!isBoringSSL ||
        (has(hashes, 'sha3-256') &&
         has(hashes, 'shake256'))),
    'SHA-1': has(hashes, 'sha1'),
    'SHA-256': has(hashes, 'sha256'),
    'SHA-384': has(hashes, 'sha384'),
    'SHA-512': has(hashes, 'sha512'),
    'SHA3-256': has(hashes, 'sha3-256'),
    'SHA3-384': has(hashes, 'sha3-384'),
    'SHA3-512': has(hashes, 'sha3-512'),
    'TurboSHAKE128': !fips,
    'TurboSHAKE256': !fips,
    'X25519': isKeyAlgorithmAvailable('X25519'),
    'X448': isKeyAlgorithmAvailable('X448'),
  };
}

// Experimental algorithms that emit warnings.
const experimentalAlgorithmsWithWarnings = [
  'AES-OCB',
  'Argon2d',
  'Argon2i',
  'Argon2id',
  'cSHAKE128',
  'cSHAKE256',
  'Ed448',
  'KMAC128',
  'KMAC256',
  'ML-KEM-512',
  'MLKEM768-P256',
  'MLKEM1024-P384',
  'SHA3-256',
  'SHA3-384',
  'SHA3-512',
  'TurboSHAKE128',
  'TurboSHAKE256',
  'KT128',
  'KT256',
  'X448',
  // Ships unflagged in Chromium >= 155
  // 'ChaCha20-Poly1305'
  // 'ML-DSA-44'
  // 'ML-DSA-65'
  // 'ML-DSA-87'
  // 'ML-KEM-768'
  // 'ML-KEM-1024'
  // 'MLKEM768-X25519'
];

// Transform the algorithm definitions into the operation-keyed structure
// Also builds a parallel Map<UPPERCASED_NAME, canonicalName> per operation
// for O(1) case-insensitive algorithm name lookup in normalizeAlgorithm.
function createSupportedAlgorithms(algorithmDefs) {
  const conditionalAlgorithms = getConditionalAlgorithms();
  // Detached below rather than declared `__proto__: null`: V8 puts that
  // literal form in dictionary mode, slowing every registry lookup.
  const result = {};
  const nameMap = {};

  const algorithmEntries = ObjectEntries(algorithmDefs);
  for (let i = 0; i < algorithmEntries.length; i++) {
    const { 0: algorithmName, 1: operations } = algorithmEntries[i];
    // Skip algorithms that are conditionally not supported
    if (ObjectPrototypeHasOwnProperty(conditionalAlgorithms, algorithmName) &&
        !conditionalAlgorithms[algorithmName]) {
      continue;
    }

    const operationEntries = ObjectEntries(operations);
    for (let j = 0; j < operationEntries.length; j++) {
      const { 0: operation, 1: dict } = operationEntries[j];
      result[operation] ||= {};
      nameMap[operation] ||= new SafeMap();
      nameMap[operation].set(StringPrototypeToUpperCase(algorithmName), algorithmName);

      // Add warnings for select experimental algorithms
      if (ArrayPrototypeIncludes(experimentalAlgorithmsWithWarnings, algorithmName)) {
        ObjectDefineProperty(result[operation], algorithmName, {
          get() {
            emitExperimentalWarning(`The ${algorithmName} Web Crypto API algorithm`);
            return dict;
          },
          __proto__: null,
          enumerable: true,
        });
      } else {
        result[operation][algorithmName] = dict;
      }
    }
  }

  const operations = ObjectKeys(result);
  for (let i = 0; i < operations.length; i++) {
    ObjectSetPrototypeOf(result[operations[i]], null);
  }
  ObjectSetPrototypeOf(result, null);
  ObjectSetPrototypeOf(nameMap, null);

  return { algorithms: result, nameMap };
}

let supportedAlgorithms;
let supportedAlgorithmsGeneration;
function getSupportedAlgorithms() {
  const generation = getFipsCryptoGeneration();
  if (supportedAlgorithmsGeneration !== generation) {
    supportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions);
    supportedAlgorithmsGeneration = generation;
  }
  return supportedAlgorithms;
}

const simpleAlgorithmDictionaries = {
  AesCbcParams: { iv: 'BufferSource' },
  AesCtrParams: { counter: 'BufferSource' },
  AeadParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
  // publicExponent is not strictly a BufferSource but it is a Uint8Array that we normalize
  // this way
  RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier', publicExponent: 'BufferSource' },
  EcKeyGenParams: {},
  HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
  RsaPssParams: {},
  EcdsaParams: { hash: 'HashAlgorithmIdentifier' },
  HmacImportParams: { hash: 'HashAlgorithmIdentifier' },
  HkdfParams: {
    hash: 'HashAlgorithmIdentifier',
    salt: 'BufferSource',
    info: 'BufferSource',
  },
  ContextParams: { context: 'BufferSource' },
  Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' },
  RsaOaepParams: { label: 'BufferSource' },
  RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' },
  EcKeyImportParams: {},
  CShakeParams: {
    functionName: 'BufferSource',
    customization: 'BufferSource',
  },
  Argon2Params: {
    associatedData: 'BufferSource',
    nonce: 'BufferSource',
    secretValue: 'BufferSource',
  },
  KmacParams: {
    customization: 'BufferSource',
  },
  KangarooTwelveParams: {
    customization: 'BufferSource',
  },
  TurboShakeParams: {},
};

// Pre-compute ObjectKeys() for each dictionary entry at module init
// to avoid allocating a new keys array on every normalizeAlgorithm call.
const simpleAlgorithmDictionaryEntries =
  ObjectEntries(simpleAlgorithmDictionaries);
for (let i = 0; i < simpleAlgorithmDictionaryEntries.length; i++) {
  const { 0: name, 1: types } = simpleAlgorithmDictionaryEntries[i];
  simpleAlgorithmDictionaries[name] = { keys: ObjectKeys(types), types };
}
// See createSupportedAlgorithms() for why this is detached here.
ObjectSetPrototypeOf(simpleAlgorithmDictionaries, null);

function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
  if (getBufferSourceByteLength(data) > max) {
    throw lazyDOMException(
      `${name} must be at most ${max} bytes`,
      'OperationError');
  }
}

function validateKmacKeyLength(length) {
  if (length < 32 || length % 8 !== 0)
    throw lazyDOMException('Invalid key length', 'NotSupportedError');
}

/**
 * Converts a bit length to the number of bytes needed to contain it.
 * Non-byte lengths are rounded up to the next byte.
 * @param {number} length
 * @returns {number}
 */
function numBitsToBytes(length) {
  return MathFloor(length / 8) + MathFloor((7 + (length % 8)) / 8);
}

/**
 * Copies `bytes` up to the byte length needed for `length` bits, then clears
 * unused least-significant bits in the final byte.
 * @param {number} length
 * @param {ArrayBuffer|ArrayBufferView} bytes
 * @returns {Uint8Array}
 */
function truncateToBitLength(length, bytes) {
  const lengthBytes = numBitsToBytes(length);
  const isView = ArrayBufferIsView(bytes);
  const byteView = isView ?
    new Uint8Array(
      getDataViewOrTypedArrayBuffer(bytes),
      getDataViewOrTypedArrayByteOffset(bytes),
      MathMin(lengthBytes, getDataViewOrTypedArrayByteLength(bytes)),
    ) :
    new Uint8Array(bytes, 0,
                   MathMin(lengthBytes, ArrayBufferPrototypeGetByteLength(bytes)));
  const result = new Uint8Array(byteView);

  const remainder = length % 8;
  if (remainder !== 0)
    result[lengthBytes - 1] &= (0xff << (8 - remainder)) & 0xff;

  return result;
}

let webidl;

// Keep this as a regular object. The WebIDL converters read and spread these
// options on the normalizeAlgorithm hot path, and a null-prototype object
// measurably regresses benchmark/misc/webcrypto-webidl normalizeAlgorithm-*.
const kNormalizeAlgorithmOpts = {
  prefix: 'Failed to normalize algorithm',
  context: 'passed algorithm',
};

// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm
// adapted for Node.js from Deno's implementation
// https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195
function normalizeAlgorithm(algorithm, op) {
  if (typeof algorithm === 'string')
    return normalizeAlgorithm({ name: algorithm }, op);

  webidl ??= require('internal/crypto/webidl');

  // 2. 3.
  const initialAlg = webidl.converters.Algorithm(algorithm,
                                                 kNormalizeAlgorithmOpts);
  const { algorithms, nameMap } = getSupportedAlgorithms();
  const registeredAlgorithms = algorithms[op];
  // 4.
  let algName = initialAlg.name;

  // 5. Case-insensitive lookup via pre-built Map (O(1) instead of O(n)).
  const canonicalName = nameMap[op]?.get(
    StringPrototypeToUpperCase(algName));
  if (canonicalName === undefined)
    throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');

  // Registered names are ASCII. Only check characters when case folding was
  // needed, so Unicode characters such as U+017F cannot match ASCII names.
  if (algName !== canonicalName) {
    for (let i = 0; i < algName.length; i++) {
      if (StringPrototypeCharCodeAt(algName, i) > 0x7f)
        throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
    }
  }

  algName = canonicalName;
  const desiredType = registeredAlgorithms[algName];

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

  // 6.
  const normalizedAlgorithm = webidl.algorithmConverters[desiredType](
    algorithm,
    kNormalizeAlgorithmOpts,
  );
  // 7.
  normalizedAlgorithm.name = algName;

  // 9. 10. Pre-computed keys and types from simpleAlgorithmDictionaries.
  const dictMeta = simpleAlgorithmDictionaries[desiredType];
  if (dictMeta) {
    const { keys: dictKeys, types: dictTypes } = dictMeta;
    for (let i = 0; i < dictKeys.length; i++) {
      const member = dictKeys[i];
      const idlType = dictTypes[member];
      const idlValue = normalizedAlgorithm[member];
      // 3.
      if (idlType === 'BufferSource' && idlValue) {
        normalizedAlgorithm[member] = new Uint8Array(
          getBufferSourceBytes(idlValue),
        );
      } else if (idlType === 'HashAlgorithmIdentifier') {
        const hash = normalizeAlgorithm(idlValue, 'digest');
        if (!StringPrototypeStartsWith(hash.name, 'SHA')) {
          throw lazyDOMException(
            `Only SHA hashes are supported in ${desiredType}`, 'NotSupportedError');
        }
        if (isBoringSSL && StringPrototypeStartsWith(hash.name, 'SHA3-')) {
          throw lazyDOMException(
            'SHA-3 is only supported for digest operations',
            'NotSupportedError');
        }
        normalizedAlgorithm[member] = hash;
      } else if (idlType === 'AlgorithmIdentifier') {
        // This extension point is not used by any supported algorithm (yet?)
        throw lazyDOMException('Not implemented.', 'NotSupportedError');
      }
    }
  }

  return normalizedAlgorithm;
}

function validateAlgorithm(algorithm, op) {
  // HMAC's get key length operation handles zero with a TypeError, while
  // HmacImportParams otherwise uses the import operation's DataError.
  if (op === 'get key length' && algorithm.name === 'HMAC')
    return;
  webidl ??= require('internal/crypto/webidl');
  const desiredType = getSupportedAlgorithms().algorithms[op]?.[algorithm.name];
  webidl.validators[desiredType]?.(algorithm);
}

function getDataViewOrTypedArrayBuffer(V) {
  return isDataView(V) ?
    DataViewPrototypeGetBuffer(V) : TypedArrayPrototypeGetBuffer(V);
}

function getDataViewOrTypedArrayByteOffset(V) {
  return isDataView(V) ?
    DataViewPrototypeGetByteOffset(V) : TypedArrayPrototypeGetByteOffset(V);
}

function getDataViewOrTypedArrayByteLength(V) {
  return isDataView(V) ?
    DataViewPrototypeGetByteLength(V) : TypedArrayPrototypeGetByteLength(V);
}

function getBufferSourceByteLength(V) {
  // ArrayBuffer and typed array lengths already become zero when detached.
  if (!ArrayBufferIsView(V))
    return ArrayBufferPrototypeGetByteLength(V);
  if (!isDataView(V))
    return TypedArrayPrototypeGetByteLength(V);
  const buffer = DataViewPrototypeGetBuffer(V);
  if (isArrayBuffer(buffer) && ArrayBufferPrototypeGetDetached(buffer))
    return 0;
  return DataViewPrototypeGetByteLength(V);
}

function getBufferSourceBytes(V) {
  if (!ArrayBufferIsView(V)) {
    const length = ArrayBufferPrototypeGetByteLength(V);
    if (length === 0 && ArrayBufferPrototypeGetDetached(V))
      return new Uint8Array(0);
    return new Uint8Array(V, 0, length);
  }

  let buffer;
  let offset;
  let length;
  if (isDataView(V)) {
    buffer = DataViewPrototypeGetBuffer(V);
    if (isArrayBuffer(buffer) && ArrayBufferPrototypeGetDetached(buffer))
      return new Uint8Array(0);
    offset = DataViewPrototypeGetByteOffset(V);
    length = DataViewPrototypeGetByteLength(V);
  } else {
    buffer = TypedArrayPrototypeGetBuffer(V);
    offset = TypedArrayPrototypeGetByteOffset(V);
    length = TypedArrayPrototypeGetByteLength(V);
    if (length === 0 && isArrayBuffer(buffer) &&
        ArrayBufferPrototypeGetDetached(buffer))
      return new Uint8Array(0);
  }
  return new Uint8Array(buffer, offset, length);
}

function getOptionalByteLength(V) {
  return V === undefined ? 0 : TypedArrayPrototypeGetByteLength(V);
}

function hasAnyNotIn(set, checks) {
  for (const s of set)
    if (!ArrayPrototypeIncludes(checks, s))
      return true;
  return false;
}

const validateByteSource = hideStackFrames((val, name) => {
  val = toBuf(val);

  if (isAnyArrayBuffer(val) || isArrayBufferView(val))
    return val;

  throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
    name,
    [
      'string',
      'ArrayBuffer',
      'TypedArray',
      'DataView',
      'Buffer',
    ],
    val);
});

/**
 * @template T
 * @typedef {{ run(): Promise<T> }} WebCryptoJob
 */

/**
 * CryptoJob constructors can synchronously throw while running their native
 * AdditionalConfig hook. WebCrypto needs those operation-specific setup
 * failures to reject with an OperationError.
 * @template T
 * @param {() => WebCryptoJob<T>} getJob
 * @returns {Promise<T>}
 */
function jobPromise(getJob) {
  try {
    return getJob().run();
  } catch (err) {
    return PromiseReject(lazyDOMException(
      'The operation failed for an operation-specific reason',
      { name: 'OperationError', cause: err }));
  }
}

/**
 * Temporarily shadow inherited then accessors on WebCrypto result objects.
 * Promise resolution reads "then" synchronously for thenable assimilation.
 * Returning an own undefined data property keeps that lookup from reaching
 * user-mutated prototypes.
 * @param {unknown} value
 * @returns {boolean}
 */
function prepareWebCryptoResult(value) {
  if ((value === null || typeof value !== 'object') &&
      typeof value !== 'function') {
    return false;
  }
  if (isPromise(value) || ObjectPrototypeHasOwnProperty(value, 'then'))
    return false;
  setOwnProperty(value, 'then', undefined);
  return true;
}

/**
 * Remove the temporary then property installed by prepareWebCryptoResult().
 * @param {{ then?: unknown }} value
 * @returns {void}
 */
function cleanupWebCryptoResult(value) {
  delete value.then;
}

/**
 * @template T
 * @typedef {(value: T | PromiseLike<T>) => void} WebCryptoResolve
 */

/**
 * Resolve a WebCrypto promise while inherited then accessors are shadowed.
 * @template T
 * @param {WebCryptoResolve<T>} resolve
 * @param {T | PromiseLike<T>} value
 * @returns {void}
 */
function resolveWebCryptoResult(resolve, value) {
  const shouldCleanupResult = prepareWebCryptoResult(value);
  try {
    resolve(value);
  } finally {
    if (shouldCleanupResult)
      cleanupWebCryptoResult(value);
  }
}

/**
 * Run a WebCrypto promise reaction and settle the outer promise.
 * @template T
 * @template TResult
 * @param {((value: T) => TResult | PromiseLike<TResult>) | undefined} handler
 * @param {WebCryptoResolve<T | TResult>} resolve
 * @param {(reason?: unknown) => void} reject
 * @param {T} value
 * @param {boolean} isRejected
 * @returns {void}
 */
function settleJobPromise(handler, resolve, reject, value, isRejected) {
  try {
    if (typeof handler === 'function') {
      resolveWebCryptoResult(resolve, handler(value));
    } else if (isRejected) {
      reject(value);
    } else {
      resolveWebCryptoResult(resolve, value);
    }
  } catch (err) {
    reject(err);
  }
}

/**
 * Promise.prototype.then gets promise.constructor to determine the result
 * promise's species. These promises are internal WebCrypto intermediates, so
 * make that lookup stay on the promise itself instead of user-mutated state.
 * @template T
 * @template [TResult1=T]
 * @template [TResult2=never]
 * @param {Promise<T>} promise
 * @param {((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined} [onFulfilled]
 * @param {((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined} [onRejected]
 * @returns {Promise<TResult1 | TResult2>}
 */
function jobPromiseThen(promise, onFulfilled, onRejected) {
  const {
    promise: resultPromise,
    resolve,
    reject,
  } = PromiseWithResolvers();
  setOwnProperty(promise, 'constructor', undefined);
  PromisePrototypeThen(
    promise,
    (value) => settleJobPromise(onFulfilled, resolve, reject, value, false),
    (value) => settleJobPromise(onRejected, resolve, reject, value, true));
  return resultPromise;
}

// In WebCrypto, the publicExponent option in RSA is represented as a
// WebIDL "BigInteger"... that is, a Uint8Array that allows an arbitrary
// number of leading zero bits. Our conventional APIs for reading
// an unsigned int from a Buffer are not adequate. The implementation
// here is adapted from the chromium implementation here:
// https://github.com/chromium/chromium/blob/HEAD/third_party/blink/public/platform/web_crypto_algorithm_params.h, but ported to JavaScript
// Throws an OperationError if the value does not fit in an unsigned 32-bit integer.
function bigIntArrayToUnsignedInt(input) {
  let result = 0;
  const length = TypedArrayPrototypeGetLength(input);

  for (let n = 0; n < length; ++n) {
    const n_reversed = length - n - 1;
    if (n_reversed >= 4 && input[n]) {
      throw lazyDOMException(
        'algorithm.publicExponent must fit in an unsigned 32-bit integer',
        'OperationError');
    }
    result |= input[n] << 8 * n_reversed;
  }

  return result >>> 0;
}

function bigIntArrayToUnsignedBigInt(input) {
  let result = 0n;
  const length = TypedArrayPrototypeGetLength(input);

  for (let n = 0; n < length; ++n) {
    const n_reversed = length - n - 1;
    result |= BigInt(input[n]) << 8n * BigInt(n_reversed);
  }

  return result;
}

function getStringOption(options, key) {
  let value;
  if (options && (value = options[key]) != null)
    validateString(value, `options.${key}`);
  return value;
}

// Must be at most 31 entries.
const kCanonicalUsageOrder = [
  'encrypt', 'decrypt',
  'sign', 'verify',
  'deriveKey', 'deriveBits',
  'wrapKey', 'unwrapKey',
  'encapsulateKey', 'encapsulateBits',
  'decapsulateKey', 'decapsulateBits',
];

const kUsageMasks = {
  __proto__: null,
};
const kUsageByMask = {
  __proto__: null,
};
// Derive both lookup tables from kCanonicalUsageOrder so adding a new
// usage only requires updating the canonical list above. The numeric
// mask uses the usage's canonical index as its bit position.
for (let n = 0; n < kCanonicalUsageOrder.length; n++) {
  const usage = kCanonicalUsageOrder[n];
  const mask = 1 << n;
  kUsageMasks[usage] = mask;
  kUsageByMask[mask] = usage;
}

/**
 * Collects a key usage list into a set.
 * @param {string[]} usages
 * @returns {SafeSet<string>}
 */
function toUsagesSet(usages) {
  const usagesSet = new SafeSet();
  for (let n = 0; n < usages.length; n++) {
    usagesSet.add(usages[n]);
  }
  return usagesSet;
}

/**
 * Returns a bit mask representing the usages from `usageSet`.
 * @param {SafeSet<string>} usageSet
 * @returns {number}
 */
function getUsagesMask(usageSet) {
  // No usages is a valid state for some public keys, represented by a
  // zero mask.
  if (usageSet.size === 0) return 0;
  let mask = 0;
  for (const usage of usageSet) {
    mask |= kUsageMasks[usage];
  }
  return mask;
}

/**
 * Returns whether `mask` contains `usage`.
 * @param {number} mask
 * @param {string} usage
 * @returns {boolean}
 */
function hasUsage(mask, usage) {
  return (mask & kUsageMasks[usage]) !== 0;
}

/**
 * Returns the usages represented by `mask` in canonical order.
 * @param {number} mask
 * @returns {string[]}
 */
function getUsagesFromMask(mask) {
  // Short circuit most common cases, empty and single usage
  // No usages is a valid state for some public keys
  if (mask === 0) return [];
  // A mask with exactly one bit set maps directly to one usage
  if ((mask & (mask - 1)) === 0) {
    return [kUsageByMask[mask]];
  }
  // Multiple usages need to be expanded in canonical order.
  const result = [];
  for (let n = 0; n < kCanonicalUsageOrder.length; n++) {
    if (mask & (1 << n))
      ArrayPrototypePush(result, kCanonicalUsageOrder[n]);
  }
  return result;
}

function getBlockSize(name) {
  switch (name) {
    case 'SHA-1':
      // Fall through
    case 'SHA-256':
      return 512;
    case 'SHA-384':
      // Fall through
    case 'SHA-512':
      return 1024;
    case 'SHA3-256':
      // Fall through
    case 'SHA3-384':
      // Fall through
    case 'SHA3-512':
      // This interaction is not defined for now.
      // https://github.com/WICG/webcrypto-modern-algos/issues/23
      throw lazyDOMException('Explicit algorithm length member is required', 'NotSupportedError');
  }
}

function validateKeyOps(keyOps, usagesSet) {
  if (keyOps === undefined) return;
  validateArray(keyOps, 'keyData.key_ops');
  let keyOpsMask = 0;
  const seen = new SafeSet();
  for (let n = 0; n < keyOps.length; n++) {
    const op = keyOps[n];
    if (seen.has(op))
      throw lazyDOMException('Duplicate key operation', 'DataError');
    seen.add(op);
    const opMask = kUsageMasks[op];
    // Skipping unknown key ops
    if (opMask === undefined)
      continue;
    keyOpsMask |= opMask;

    // TODO(@jasnell): RFC7517 section 4.3 strong recommends validating
    // key usage combinations. Specifically, it says that unrelated key
    // ops SHOULD NOT be used together. We're not yet validating that here.
  }

  if (usagesSet !== undefined) {
    const usagesMask = getUsagesMask(usagesSet);
    if ((keyOpsMask & usagesMask) !== usagesMask) {
      throw lazyDOMException(
        'Key operations and usage mismatch',
        'DataError');
    }
  }
}

function secureHeapUsed() {
  const val = _secureHeapUsed();
  if (val === undefined)
    return { total: 0, used: 0, utilization: 0, min: 0 };
  const used = Number(_secureHeapUsed());
  const total = Number(getOptionValue('--secure-heap'));
  const min = Number(getOptionValue('--secure-heap-min'));
  const utilization = used / total;
  return { total, used, utilization, min };
}

module.exports = {
  getArrayBufferOrView,
  getBufferSourceByteLength,
  getBufferSourceBytes,
  getCiphers,
  getCurves,
  getDataViewOrTypedArrayBuffer,
  getHashes,
  getMacs,
  getOptionalByteLength,
  kHandle,
  toBuf,

  kNamedCurveAliases,
  get kSupportedAlgorithms() {
    return getSupportedAlgorithms().algorithms;
  },
  isFips,
  normalizeAlgorithm,
  normalizeHashName,
  hasAnyNotIn,
  validateByteSource,
  validateKeyOps,
  validateAlgorithm,
  jobPromise,
  jobPromiseThen,
  cleanupWebCryptoResult,
  prepareWebCryptoResult,
  validateKmacKeyLength,
  resolveWebCryptoResult,
  validateMaxBufferLength,
  numBitsToBytes,
  truncateToBitLength,
  bigIntArrayToUnsignedBigInt,
  bigIntArrayToUnsignedInt,
  getBlockSize,
  getStringOption,
  getUsagesMask,
  getUsagesFromMask,
  toUsagesSet,
  hasUsage,
  secureHeapUsed,
  getCachedHashId,
  getHashCache,
  getCachedMacId,
  getMacCache,
  getOpenSSLSecLevel,
};