// Copyright 2018-2026 the Deno authors. MIT license.

#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]

use std::future::Future;
use std::rc::Rc;

use aws_lc_rs::signature::Ed25519KeyPair;
use deno_core::JsBuffer;
use deno_core::OpState;
use deno_core::StringOrBuffer;
use deno_core::convert::Uint8Array;
use deno_core::op2;
use deno_core::unsync::spawn_blocking;
use deno_error::JsErrorBox;
use elliptic_curve::sec1::ToEncodedPoint;
use hkdf::Hkdf;
use keys::AsymmetricPrivateKey;
use keys::AsymmetricPublicKey;
use keys::EcPrivateKey;
use keys::EcPublicKey;
use keys::KeyObjectHandle;
use num_bigint::BigInt;
use num_bigint_dig::BigUint;
use p224::NistP224;
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
use rand::Rng;
use rand::distributions::Distribution;
use rand::distributions::Uniform;
use rsa::Oaep;
use rsa::Pkcs1v15Encrypt;
use rsa::RsaPrivateKey;
use rsa::RsaPublicKey;
use rsa::hazmat::rsa_decrypt_and_check;
use rsa::hazmat::rsa_encrypt;
use rsa::pkcs1::DecodeRsaPrivateKey;
use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::pkcs8::DecodePrivateKey;
use rsa::pkcs8::DecodePublicKey;
use rsa::traits::PublicKeyParts;

pub mod cipher;
pub(crate) mod dh;
pub mod digest;
pub mod keys;
pub(crate) mod md5_sha1;
pub(crate) mod pkcs3;
pub(crate) mod primes;
pub mod sign;
pub mod x509;

use self::digest::match_fixed_digest_with_eager_block_buffer;

deno_core::extension!(
  deno_node_crypto,
  ops = [
    op_node_check_prime_async,
    op_node_check_prime_bytes_async,
    op_node_check_prime_bytes,
    op_node_check_prime,
    op_node_aes_wrap_key,
    op_node_aes_unwrap_key,
    op_node_cipheriv_encrypt,
    op_node_cipheriv_final,
    op_node_cipheriv_set_aad,
    op_node_cipheriv_take,
    op_node_create_cipheriv,
    op_node_create_decipheriv,
    op_node_create_hash,
    op_node_decipheriv_decrypt,
    op_node_decipheriv_final,
    op_node_decipheriv_set_aad,
    op_node_decipheriv_auth_tag,
    op_node_dh_check,
    op_node_dh_compute_secret,
    op_node_diffie_hellman,
    op_node_ecdh_compute_public_key,
    op_node_ecdh_compute_secret,
    op_node_ecdh_encode_pubkey,
    op_node_ecdh_generate_keys,
    op_node_ecdh_validate_private_key,
    op_node_ecdh_validate_public_key,
    op_node_fill_random_async,
    op_node_fill_random,
    op_node_gen_prime_async,
    op_node_gen_prime,
    op_node_get_hash_size,
    op_node_get_hashes,
    op_node_hash_clone,
    op_node_hash_digest_hex,
    op_node_hash_digest,
    op_node_hash_update_str,
    op_node_hash_update,
    op_node_hkdf_async,
    op_node_hkdf,
    op_node_pbkdf2_async,
    op_node_pbkdf2,
    op_node_pbkdf2_validate,
    op_node_private_decrypt,
    op_node_private_encrypt,
    op_node_public_decrypt,
    op_node_public_encrypt,
    op_node_validate_oaep_hash,
    op_node_random_int,
    op_node_scrypt_async,
    op_node_scrypt_sync,
    op_node_sign,
    op_node_sign_ed25519,
    op_node_sign_ed448,
    op_node_verify,
    op_node_verify_ed25519,
    op_node_verify_ed448,
    op_node_verify_spkac,
    op_node_cert_export_public_key,
    op_node_cert_export_challenge,
    keys::op_node_create_private_key,
    keys::op_node_create_ed_raw,
    keys::op_node_create_rsa_jwk,
    keys::op_node_create_ec_jwk,
    keys::op_node_create_public_key,
    keys::op_node_create_secret_key,
    keys::op_node_derive_public_key_from_private_key,
    keys::op_node_dh_keys_generate_and_export,
    keys::op_node_export_private_key_der,
    keys::op_node_export_private_key_jwk,
    keys::op_node_export_private_key_pem,
    keys::op_node_export_public_key_der,
    keys::op_node_export_public_key_pem,
    keys::op_node_export_public_key_jwk,
    keys::op_node_export_secret_key_b64url,
    keys::op_node_export_secret_key,
    keys::op_node_generate_dh_group_key_async,
    keys::op_node_generate_dh_group_key,
    keys::op_node_generate_dh_key_async,
    keys::op_node_generate_dh_key,
    keys::op_node_generate_dsa_key_async,
    keys::op_node_generate_dsa_key,
    keys::op_node_generate_ec_key_async,
    keys::op_node_generate_ec_key,
    keys::op_node_generate_ed25519_key_async,
    keys::op_node_generate_ed25519_key,
    keys::op_node_generate_x448_key_async,
    keys::op_node_generate_x448_key,
    keys::op_node_generate_ed448_key_async,
    keys::op_node_generate_ed448_key,
    keys::op_node_generate_rsa_key_async,
    keys::op_node_generate_rsa_key,
    keys::op_node_generate_rsa_pss_key,
    keys::op_node_generate_rsa_pss_key_async,
    keys::op_node_generate_secret_key_async,
    keys::op_node_generate_secret_key,
    keys::op_node_generate_x25519_key_async,
    keys::op_node_generate_x25519_key,
    keys::op_node_get_asymmetric_key_details,
    keys::op_node_get_asymmetric_key_type,
    keys::op_node_get_private_key_from_pair,
    keys::op_node_get_public_key_from_pair,
    keys::op_node_get_symmetric_key_size,
    keys::op_node_key_equals,
    keys::op_node_key_type,
    keys::op_node_load_pfx,
    keys::op_node_validate_crl,
    x509::op_node_x509_parse,
    x509::op_node_x509_ca,
    x509::op_node_x509_check_email,
    x509::op_node_x509_check_host,
    x509::op_node_x509_fingerprint,
    x509::op_node_x509_fingerprint256,
    x509::op_node_x509_fingerprint512,
    x509::op_node_x509_get_issuer,
    x509::op_node_x509_get_subject,
    x509::op_node_x509_get_valid_from,
    x509::op_node_x509_get_valid_to,
    x509::op_node_x509_get_serial_number,
    x509::op_node_x509_key_usage,
    x509::op_node_x509_public_key,
    x509::op_node_x509_to_string,
    x509::op_node_x509_get_raw,
    x509::op_node_x509_get_subject_alt_name,
    x509::op_node_x509_check_ip,
    x509::op_node_x509_check_issued,
    x509::op_node_x509_check_private_key,
    x509::op_node_x509_verify,
    x509::op_node_x509_get_info_access,
    x509::op_node_x509_get_signature_algorithm_name,
    x509::op_node_x509_get_signature_algorithm_oid,
    x509::op_node_x509_to_legacy_object,
  ],
  objects = [digest::Hasher,],
);

#[op2(fast)]
pub fn op_node_check_prime(
  #[bigint] num: i64,
  #[number] checks: usize,
) -> bool {
  primes::is_probably_prime(&BigInt::from(num), checks)
}

#[op2]
pub fn op_node_check_prime_bytes(
  #[anybuffer] bytes: &[u8],
  #[number] checks: usize,
) -> bool {
  let candidate = BigInt::from_bytes_be(num_bigint::Sign::Plus, bytes);
  primes::is_probably_prime(&candidate, checks)
}

#[op2]
pub async fn op_node_check_prime_async(
  #[bigint] num: i64,
  #[number] checks: usize,
) -> Result<bool, tokio::task::JoinError> {
  // TODO(@littledivy): use rayon for CPU-bound tasks
  spawn_blocking(move || primes::is_probably_prime(&BigInt::from(num), checks))
    .await
}

#[op2]
pub fn op_node_check_prime_bytes_async(
  #[anybuffer] bytes: &[u8],
  #[number] checks: usize,
) -> impl Future<Output = Result<bool, tokio::task::JoinError>> + use<> {
  let candidate = BigInt::from_bytes_be(num_bigint::Sign::Plus, bytes);
  // TODO(@littledivy): use rayon for CPU-bound tasks
  async move {
    spawn_blocking(move || primes::is_probably_prime(&candidate, checks)).await
  }
}

#[op2]
#[cppgc]
pub fn op_node_create_hash(
  #[string] algorithm: &str,
  output_length: Option<u32>,
) -> Result<digest::Hasher, digest::HashError> {
  digest::Hasher::new(algorithm, output_length.map(|l| l as usize))
}

#[op2]
pub fn op_node_get_hashes() -> Vec<&'static str> {
  digest::Hash::get_hashes()
}

#[op2]
pub fn op_node_get_hash_size(#[string] algorithm: &str) -> Option<u8> {
  digest::Hash::get_size(algorithm)
}

#[op2(fast)]
pub fn op_node_hash_update(
  #[cppgc] hasher: &digest::Hasher,
  #[buffer] data: &[u8],
) -> bool {
  hasher.update(data)
}

#[op2(fast)]
pub fn op_node_hash_update_str(
  #[cppgc] hasher: &digest::Hasher,
  #[string] data: &str,
) -> bool {
  hasher.update(data.as_bytes())
}

#[op2]
#[buffer]
pub fn op_node_hash_digest(
  #[cppgc] hasher: &digest::Hasher,
) -> Option<Box<[u8]>> {
  hasher.digest()
}

#[op2]
#[string]
pub fn op_node_hash_digest_hex(
  #[cppgc] hasher: &digest::Hasher,
) -> Option<String> {
  let digest = hasher.digest()?;
  Some(faster_hex::hex_string(&digest))
}

#[op2]
#[cppgc]
pub fn op_node_hash_clone(
  #[cppgc] hasher: &digest::Hasher,
  output_length: Option<u32>,
) -> Result<Option<digest::Hasher>, digest::HashError> {
  hasher.clone_inner(output_length.map(|l| l as usize))
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum PrivateEncryptDecryptError {
  #[class(generic)]
  #[error(transparent)]
  Pkcs8(#[from] pkcs8::Error),
  #[class(generic)]
  #[error(transparent)]
  Spki(#[from] spki::Error),
  #[class(generic)]
  #[error(transparent)]
  Rsa(#[from] rsa::Error),
  #[class(type)]
  #[error("Unknown padding")]
  UnknownPadding,
  #[class(generic)]
  #[property("code" = "ERR_OSSL_EVP_INVALID_DIGEST")]
  #[error(
    "error:060800A3:digital envelope routines::unknown message digest algorithm"
  )]
  InvalidDigest,
  #[class(generic)]
  #[property("code" = "ERR_OSSL_RSA_OAEP_DECODING_ERROR")]
  #[error(
    "error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error"
  )]
  OaepDecodingError,
  #[class(generic)]
  #[property("code" = "ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED")]
  #[error("error:07880109:common libcrypto routines::interrupted or cancelled")]
  EncryptedKeyRequiresPassphrase,
}

/// PKCS#1 v1.5 type 1 padding for private key encryption (signing).
/// EM = 0x00 || 0x01 || PS || 0x00 || M
/// where PS is filled with 0xFF bytes and has length k - mLen - 3.
fn pkcs1v15_type1_pad(
  msg: &[u8],
  k: usize,
) -> Result<Vec<u8>, PrivateEncryptDecryptError> {
  if msg.len() > k - 11 {
    return Err(PrivateEncryptDecryptError::Rsa(rsa::Error::MessageTooLong));
  }
  let mut em = vec![0xffu8; k];
  em[0] = 0;
  em[1] = 1;
  em[k - msg.len() - 1] = 0;
  em[k - msg.len()..].copy_from_slice(msg);
  Ok(em)
}

/// Remove PKCS#1 v1.5 type 1 padding.
/// EM = 0x00 || 0x01 || PS (0xFF bytes) || 0x00 || M
fn pkcs1v15_type1_unpad(
  em: &[u8],
  k: usize,
) -> Result<Vec<u8>, PrivateEncryptDecryptError> {
  if k < 11 || em.len() != k || em[0] != 0 || em[1] != 1 {
    return Err(PrivateEncryptDecryptError::Rsa(rsa::Error::Decryption));
  }
  // Find the 0x00 separator after the 0xFF padding
  let mut sep = None;
  for (i, &byte) in em.iter().enumerate().take(k).skip(2) {
    if byte == 0 {
      sep = Some(i);
      break;
    }
    if byte != 0xff {
      return Err(PrivateEncryptDecryptError::Rsa(rsa::Error::Decryption));
    }
  }
  let sep =
    sep.ok_or(PrivateEncryptDecryptError::Rsa(rsa::Error::Decryption))?;
  if sep < 10 {
    // PS must be at least 8 bytes
    return Err(PrivateEncryptDecryptError::Rsa(rsa::Error::Decryption));
  }
  Ok(em[sep + 1..].to_vec())
}

fn create_oaep(
  hash: Option<&str>,
  label: Option<&[u8]>,
) -> Result<Oaep, PrivateEncryptDecryptError> {
  let hash = hash.unwrap_or("sha1");
  let mut oaep = digest::match_fixed_digest!(hash, fn <D>() {
    Oaep::new::<D>()
  }, _ => {
    return Err(PrivateEncryptDecryptError::InvalidDigest);
  });
  if let Some(label) = label {
    // The rsa crate stores the label as String but only uses
    // .as_bytes() on it, so the UTF-8 invariant is not relied upon.
    // SAFETY: label bytes are only ever hashed, never interpreted as text.
    oaep.label = Some(unsafe { String::from_utf8_unchecked(label.to_vec()) });
  }
  Ok(oaep)
}

#[op2]
pub fn op_node_private_encrypt(
  #[serde] key: StringOrBuffer,
  #[serde] msg: StringOrBuffer,
  #[smi] padding: u32,
  #[string] oaep_hash: Option<String>,
  #[serde] oaep_label: Option<JsBuffer>,
) -> Result<Uint8Array, PrivateEncryptDecryptError> {
  let key = match std::str::from_utf8(&key) {
    Ok(pem) => RsaPrivateKey::from_pkcs8_pem(pem)
      .or_else(|_| rsa::pkcs1::DecodeRsaPrivateKey::from_pkcs1_pem(pem))
      .map_err(|e| PrivateEncryptDecryptError::Pkcs8(e.into()))?,
    Err(_) => RsaPrivateKey::from_pkcs8_der(&key).or_else(|_| {
      RsaPrivateKey::from_pkcs1_der(&key).map_err(pkcs8::Error::from)
    })?,
  };

  let k = key.size();
  let mut rng = rand::thread_rng();
  match padding {
    1 => {
      // PKCS1 type 1 padding + raw RSA private key operation
      let em = pkcs1v15_type1_pad(&msg, k)?;
      let int = num_bigint_dig::BigUint::from_bytes_be(&em);
      let result = rsa_decrypt_and_check(&key, Some(&mut rng), &int)?;
      let mut result_bytes = result.to_bytes_be();
      // Left-pad with zeros to key size
      while result_bytes.len() < k {
        result_bytes.insert(0, 0);
      }
      Ok(result_bytes.into())
    }
    4 => {
      let oaep = create_oaep(oaep_hash.as_deref(), oaep_label.as_deref())?;
      Ok(key.as_ref().encrypt(&mut rng, oaep, &msg)?.into())
    }
    _ => Err(PrivateEncryptDecryptError::UnknownPadding),
  }
}

#[op2]
pub fn op_node_private_decrypt(
  #[serde] key: StringOrBuffer,
  #[serde] msg: StringOrBuffer,
  #[smi] padding: u32,
  #[string] oaep_hash: Option<String>,
  #[serde] oaep_label: Option<JsBuffer>,
) -> Result<Uint8Array, PrivateEncryptDecryptError> {
  let key = match std::str::from_utf8(&key) {
    Ok(pem) => {
      if pem.contains("Proc-Type: 4,ENCRYPTED")
        || pem
          .trim_start()
          .starts_with("-----BEGIN ENCRYPTED PRIVATE KEY-----")
      {
        return Err(PrivateEncryptDecryptError::EncryptedKeyRequiresPassphrase);
      }
      RsaPrivateKey::from_pkcs8_pem(pem)
        .or_else(|_| rsa::pkcs1::DecodeRsaPrivateKey::from_pkcs1_pem(pem))
        .map_err(|e| PrivateEncryptDecryptError::Pkcs8(e.into()))?
    }
    Err(_) => RsaPrivateKey::from_pkcs8_der(&key).or_else(|_| {
      RsaPrivateKey::from_pkcs1_der(&key).map_err(pkcs8::Error::from)
    })?,
  };

  let k = key.size();
  let mut rng = rand::thread_rng();
  match padding {
    1 => Ok(key.decrypt(Pkcs1v15Encrypt, &msg)?.into()),
    3 => {
      // RSA_NO_PADDING: raw private-key exponentiation
      let int = BigUint::from_bytes_be(&msg);
      let result = rsa_decrypt_and_check(&key, Some(&mut rng), &int)?;
      let mut result_bytes = result.to_bytes_be();
      while result_bytes.len() < k {
        result_bytes.insert(0, 0);
      }
      Ok(result_bytes.into())
    }
    4 => {
      let oaep = create_oaep(oaep_hash.as_deref(), oaep_label.as_deref())?;
      key
        .decrypt(oaep, &msg)
        .map(|v| v.into())
        .map_err(|_| PrivateEncryptDecryptError::OaepDecodingError)
    }
    _ => Err(PrivateEncryptDecryptError::UnknownPadding),
  }
}

fn rsa_pub_key_from_cert_pem(pem: &str) -> Result<RsaPublicKey, pkcs8::Error> {
  use x509_parser::prelude::FromDer;
  let (_, pem_obj) = x509_parser::pem::parse_x509_pem(pem.as_bytes())
    .map_err(|_| pkcs8::Error::KeyMalformed)?;
  let spki_der = {
    let (_, cert) =
      x509_parser::prelude::X509Certificate::from_der(&pem_obj.contents)
        .map_err(|_| pkcs8::Error::KeyMalformed)?;
    cert.tbs_certificate.subject_pki.raw.to_vec()
  };
  RsaPublicKey::from_public_key_der(&spki_der)
    .map_err(|_| pkcs8::Error::KeyMalformed)
}

#[op2(fast)]
pub fn op_node_validate_oaep_hash(
  #[string] hash: &str,
) -> Result<(), PrivateEncryptDecryptError> {
  create_oaep(Some(hash), None).map(|_| ())
}

#[op2]
pub fn op_node_public_encrypt(
  #[serde] key: StringOrBuffer,
  #[serde] msg: StringOrBuffer,
  #[smi] padding: u32,
  #[string] oaep_hash: Option<String>,
  #[serde] oaep_label: Option<JsBuffer>,
) -> Result<Uint8Array, PrivateEncryptDecryptError> {
  let key = match std::str::from_utf8(&key) {
    Ok(pem) => RsaPublicKey::from_public_key_pem(pem)
      .or_else(|_| rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_pem(pem))
      .or_else(|_| {
        RsaPrivateKey::from_pkcs8_pem(pem)
          .or_else(|_| {
            rsa::pkcs1::DecodeRsaPrivateKey::from_pkcs1_pem(pem)
              .map_err(pkcs8::Error::from)
          })
          .map(|k| k.to_public_key())
      })
      .or_else(|_| rsa_pub_key_from_cert_pem(pem))
      .map_err(|e| PrivateEncryptDecryptError::Spki(e.into()))?,
    Err(_) => RsaPublicKey::from_public_key_der(&key)
      .or_else(|_| {
        RsaPublicKey::from_pkcs1_der(&key).map_err(spki::Error::from)
      })
      .or_else(|_| {
        RsaPrivateKey::from_pkcs8_der(&key)
          .or_else(|_| {
            RsaPrivateKey::from_pkcs1_der(&key).map_err(pkcs8::Error::from)
          })
          .map(|k| k.to_public_key())
          .map_err(spki::Error::from)
      })?,
  };

  let k = key.size();
  let mut rng = rand::thread_rng();
  match padding {
    1 => Ok(key.encrypt(&mut rng, Pkcs1v15Encrypt, &msg)?.into()),
    3 => {
      // RSA_NO_PADDING: raw public-key exponentiation, zero-pad input to key size
      let mut padded = vec![0u8; k];
      let copy_len = msg.len().min(k);
      padded[k - copy_len..].copy_from_slice(&msg[msg.len() - copy_len..]);
      let int = BigUint::from_bytes_be(&padded);
      let result = rsa_encrypt(&key, &int)?;
      let mut result_bytes = result.to_bytes_be();
      while result_bytes.len() < k {
        result_bytes.insert(0, 0);
      }
      Ok(result_bytes.into())
    }
    4 => {
      let oaep = create_oaep(oaep_hash.as_deref(), oaep_label.as_deref())?;
      Ok(key.encrypt(&mut rng, oaep, &msg)?.into())
    }
    _ => Err(PrivateEncryptDecryptError::UnknownPadding),
  }
}

#[op2]
pub fn op_node_public_decrypt(
  #[serde] key: StringOrBuffer,
  #[serde] msg: StringOrBuffer,
  #[smi] padding: u32,
) -> Result<Uint8Array, PrivateEncryptDecryptError> {
  let key = match std::str::from_utf8(&key) {
    Ok(pem) => RsaPublicKey::from_public_key_pem(pem)
      .or_else(|_| rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_pem(pem))
      .or_else(|_| {
        RsaPrivateKey::from_pkcs8_pem(pem)
          .or_else(|_| {
            rsa::pkcs1::DecodeRsaPrivateKey::from_pkcs1_pem(pem)
              .map_err(pkcs8::Error::from)
          })
          .map(|k| k.to_public_key())
      })
      .or_else(|_| rsa_pub_key_from_cert_pem(pem))
      .map_err(|e| PrivateEncryptDecryptError::Spki(e.into()))?,
    Err(_) => RsaPublicKey::from_public_key_der(&key)
      .or_else(|_| {
        RsaPublicKey::from_pkcs1_der(&key).map_err(spki::Error::from)
      })
      .or_else(|_| {
        RsaPrivateKey::from_pkcs8_der(&key)
          .or_else(|_| {
            RsaPrivateKey::from_pkcs1_der(&key).map_err(pkcs8::Error::from)
          })
          .map(|k| k.to_public_key())
          .map_err(spki::Error::from)
      })?,
  };

  let k = key.size();
  match padding {
    1 => {
      // Raw RSA with public key, then remove PKCS1 type 1 padding
      let int = num_bigint_dig::BigUint::from_bytes_be(&msg);
      let result = rsa_encrypt(&key, &int)?;
      let mut em = result.to_bytes_be();
      // Left-pad with zeros to key size
      while em.len() < k {
        em.insert(0, 0);
      }
      Ok(pkcs1v15_type1_unpad(&em, k)?.into())
    }
    4 => {
      // OAEP not supported for public decrypt
      Err(PrivateEncryptDecryptError::UnknownPadding)
    }
    _ => Err(PrivateEncryptDecryptError::UnknownPadding),
  }
}

#[op2]
pub fn op_node_aes_wrap_key(
  #[string] algorithm: &str,
  #[buffer] key: &[u8],
  #[buffer] iv: &[u8],
  #[buffer] data: &[u8],
) -> Result<Uint8Array, cipher::AesWrapError> {
  cipher::aes_wrap_key(algorithm, key, iv, data).map(Into::into)
}

#[op2]
pub fn op_node_aes_unwrap_key(
  #[string] algorithm: &str,
  #[buffer] key: &[u8],
  #[buffer] iv: &[u8],
  #[buffer] data: &[u8],
) -> Result<Uint8Array, cipher::AesWrapError> {
  cipher::aes_unwrap_key(algorithm, key, iv, data).map(Into::into)
}

#[op2(fast)]
#[smi]
pub fn op_node_create_cipheriv(
  state: &mut OpState,
  #[string] algorithm: &str,
  #[buffer] key: &[u8],
  #[buffer] iv: &[u8],
  #[smi] auth_tag_length: i32,
) -> Result<u32, cipher::CipherContextError> {
  let auth_tag_length = if auth_tag_length == -1 {
    None
  } else {
    Some(auth_tag_length as usize)
  };
  let context =
    cipher::CipherContext::new(algorithm, key, iv, auth_tag_length)?;
  Ok(state.resource_table.add(context))
}

#[op2(fast)]
pub fn op_node_cipheriv_set_aad(
  state: &mut OpState,
  #[smi] rid: u32,
  #[buffer] aad: &[u8],
) -> bool {
  let context = match state.resource_table.get::<cipher::CipherContext>(rid) {
    Ok(context) => context,
    Err(_) => return false,
  };
  context.set_aad(aad);
  true
}

#[op2(fast)]
pub fn op_node_cipheriv_encrypt(
  state: &mut OpState,
  #[smi] rid: u32,
  #[buffer] input: &[u8],
  #[buffer] output: &mut [u8],
) -> bool {
  let context = match state.resource_table.get::<cipher::CipherContext>(rid) {
    Ok(context) => context,
    Err(_) => return false,
  };
  if output.len() < input.len() {
    return false;
  }
  context.encrypt(input, output);
  true
}

#[op2]
pub fn op_node_cipheriv_final(
  state: &mut OpState,
  #[smi] rid: u32,
  auto_pad: bool,
  #[buffer] input: &[u8],
  #[anybuffer] output: &mut [u8],
) -> Result<Option<Uint8Array>, cipher::CipherContextError> {
  let context = state.resource_table.take::<cipher::CipherContext>(rid)?;
  let context = Rc::try_unwrap(context)
    .map_err(|_| cipher::CipherContextError::ContextInUse)?;
  context
    .r#final(auto_pad, input, output)
    .map(|tag| tag.map(Into::into))
}

#[op2]
pub fn op_node_cipheriv_take(
  state: &mut OpState,
  #[smi] rid: u32,
) -> Result<Option<Uint8Array>, cipher::CipherContextError> {
  let context = state.resource_table.take::<cipher::CipherContext>(rid)?;
  let context = Rc::try_unwrap(context)
    .map_err(|_| cipher::CipherContextError::ContextInUse)?;
  Ok(context.take_tag().map(Into::into))
}

#[op2(fast)]
#[smi]
pub fn op_node_create_decipheriv(
  state: &mut OpState,
  #[string] algorithm: &str,
  #[buffer] key: &[u8],
  #[buffer] iv: &[u8],
  #[smi] auth_tag_length: i32,
) -> Result<u32, cipher::DecipherContextError> {
  let auth_tag_length = if auth_tag_length == -1 {
    None
  } else {
    Some(auth_tag_length as usize)
  };

  let context =
    cipher::DecipherContext::new(algorithm, key, iv, auth_tag_length)?;
  Ok(state.resource_table.add(context))
}

#[op2(fast)]
pub fn op_node_decipheriv_auth_tag(
  state: &mut OpState,
  #[smi] rid: u32,
  #[smi] length: u32,
) -> Result<(), cipher::DecipherContextError> {
  let context = state.resource_table.get::<cipher::DecipherContext>(rid)?;
  context.validate_auth_tag(length as usize)
}

#[op2(fast)]
pub fn op_node_decipheriv_set_aad(
  state: &mut OpState,
  #[smi] rid: u32,
  #[buffer] aad: &[u8],
) -> bool {
  let context = match state.resource_table.get::<cipher::DecipherContext>(rid) {
    Ok(context) => context,
    Err(_) => return false,
  };
  context.set_aad(aad);
  true
}

#[op2(fast)]
pub fn op_node_decipheriv_decrypt(
  state: &mut OpState,
  #[smi] rid: u32,
  #[buffer] input: &[u8],
  #[buffer] output: &mut [u8],
) -> bool {
  let context = match state.resource_table.get::<cipher::DecipherContext>(rid) {
    Ok(context) => context,
    Err(_) => return false,
  };
  if output.len() < input.len() {
    return false;
  }
  context.decrypt(input, output);
  true
}

#[op2]
pub fn op_node_decipheriv_final(
  state: &mut OpState,
  #[smi] rid: u32,
  auto_pad: bool,
  #[buffer] input: &[u8],
  #[anybuffer] output: &mut [u8],
  #[buffer] auth_tag: &[u8],
) -> Result<(), cipher::DecipherContextError> {
  let context = state.resource_table.take::<cipher::DecipherContext>(rid)?;
  let context = Rc::try_unwrap(context)
    .map_err(|_| cipher::DecipherContextError::ContextInUse)?;
  context.r#final(auto_pad, input, output, auth_tag)
}

#[op2]
#[buffer]
pub fn op_node_sign(
  #[cppgc] handle: &KeyObjectHandle,
  #[buffer] digest: &[u8],
  #[string] digest_type: &str,
  #[smi] pss_salt_length: Option<i32>,
  #[smi] padding: Option<u32>,
  #[smi] dsa_signature_encoding: u32,
) -> Result<Box<[u8]>, sign::KeyObjectHandlePrehashedSignAndVerifyError> {
  handle.sign_prehashed(
    digest_type,
    digest,
    pss_salt_length,
    padding,
    dsa_signature_encoding,
  )
}

#[op2]
pub fn op_node_verify(
  #[cppgc] handle: &KeyObjectHandle,
  #[buffer] digest: &[u8],
  #[string] digest_type: &str,
  #[buffer] signature: &[u8],
  #[smi] pss_salt_length: Option<i32>,
  #[smi] padding: Option<u32>,
  #[smi] dsa_signature_encoding: u32,
) -> Result<bool, sign::KeyObjectHandlePrehashedSignAndVerifyError> {
  handle.verify_prehashed(
    digest_type,
    digest,
    signature,
    pss_salt_length,
    padding,
    dsa_signature_encoding,
  )
}

#[derive(Debug, PartialEq, Eq)]
#[allow(non_camel_case_types, reason = "matches Node.js error code naming")]
enum ErrorCode {
  ERR_CRYPTO_INVALID_DIGEST,
}

impl std::fmt::Display for ErrorCode {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{}", self.as_str())
  }
}

impl ErrorCode {
  pub fn as_str(&self) -> &str {
    match self {
      Self::ERR_CRYPTO_INVALID_DIGEST => "ERR_CRYPTO_INVALID_DIGEST",
    }
  }
}

impl From<ErrorCode> for deno_error::PropertyValue {
  fn from(code: ErrorCode) -> Self {
    deno_error::PropertyValue::from(code.as_str().to_string())
  }
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum Pbkdf2Error {
  #[class(type)]
  #[error("Invalid digest: {0}")]
  #[property("code" = self.code())]
  UnsupportedDigest(String),
  #[class(inherit)]
  #[error(transparent)]
  Join(#[from] tokio::task::JoinError),
}

impl Pbkdf2Error {
  fn code(&self) -> ErrorCode {
    match self {
      Self::UnsupportedDigest(_) => ErrorCode::ERR_CRYPTO_INVALID_DIGEST,
      Self::Join(_) => unreachable!(),
    }
  }
}

fn pbkdf2_sync(
  password: &[u8],
  salt: &[u8],
  iterations: u32,
  algorithm_name: &str,
  derived_key: &mut [u8],
) -> Result<(), Pbkdf2Error> {
  match_fixed_digest_with_eager_block_buffer!(
    algorithm_name,
    fn <D>() {
      pbkdf2::pbkdf2_hmac::<D>(password, salt, iterations, derived_key);
      Ok(())
    },
    _ => {
      Err(Pbkdf2Error::UnsupportedDigest(algorithm_name.to_string()))
    }
  )
}

#[op2]
pub fn op_node_pbkdf2(
  #[anybuffer] password: &[u8],
  #[anybuffer] salt: &[u8],
  #[smi] iterations: u32,
  #[string] digest: &str,
  #[buffer] derived_key: &mut [u8],
) -> bool {
  pbkdf2_sync(password, salt, iterations, digest, derived_key).is_ok()
}

#[op2(fast)]
pub fn op_node_pbkdf2_validate(
  #[string] digest: &str,
) -> Result<(), Pbkdf2Error> {
  // Validate the digest algorithm name
  match_fixed_digest_with_eager_block_buffer!(
    digest,
    fn <_D>() {
      Ok(())
    },
    _ => {
      Err(Pbkdf2Error::UnsupportedDigest(digest.to_string()))
    }
  )
}

#[op2]
pub async fn op_node_pbkdf2_async(
  #[anybuffer] password: JsBuffer,
  #[anybuffer] salt: JsBuffer,
  #[smi] iterations: u32,
  #[string] digest: String,
  #[number] keylen: usize,
) -> Result<Uint8Array, Pbkdf2Error> {
  spawn_blocking(move || {
    let mut derived_key = vec![0; keylen];
    pbkdf2_sync(&password, &salt, iterations, &digest, &mut derived_key)
      .map(|_| derived_key.into())
  })
  .await?
}

#[op2(fast)]
pub fn op_node_fill_random(#[buffer] buf: &mut [u8]) {
  rand::thread_rng().fill(buf);
}

#[op2]
pub async fn op_node_fill_random_async(#[smi] len: i32) -> Uint8Array {
  spawn_blocking(move || {
    let mut buf = vec![0u8; len as usize];
    rand::thread_rng().fill(&mut buf[..]);
    buf.into()
  })
  .await
  .unwrap()
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum HkdfError {
  #[class(type)]
  #[error("expected secret key")]
  ExpectedSecretKey,
  #[class(type)]
  #[error("HKDF-Expand failed")]
  HkdfExpandFailed,
  #[class(type)]
  #[error("Unsupported digest: {0}")]
  UnsupportedDigest(String),
  #[class(inherit)]
  #[error(transparent)]
  Join(#[from] tokio::task::JoinError),
}

fn hkdf_sync(
  digest_algorithm: &str,
  handle: &KeyObjectHandle,
  salt: &[u8],
  info: &[u8],
  okm: &mut [u8],
) -> Result<(), HkdfError> {
  let Some(ikm) = handle.as_secret_key() else {
    return Err(HkdfError::ExpectedSecretKey);
  };

  match_fixed_digest_with_eager_block_buffer!(
    digest_algorithm,
    fn <D>() {
      let hk = Hkdf::<D>::new(Some(salt), ikm);
      hk.expand(info, okm)
        .map_err(|_| HkdfError::HkdfExpandFailed)
    },
    _ => {
      Err(HkdfError::UnsupportedDigest(digest_algorithm.to_string()))
    }
  )
}

#[op2(fast)]
pub fn op_node_hkdf(
  #[string] digest_algorithm: &str,
  #[cppgc] handle: &KeyObjectHandle,
  #[buffer] salt: &[u8],
  #[buffer] info: &[u8],
  #[buffer] okm: &mut [u8],
) -> Result<(), HkdfError> {
  hkdf_sync(digest_algorithm, handle, salt, info, okm)
}

#[op2]
pub async fn op_node_hkdf_async(
  #[string] digest_algorithm: String,
  #[cppgc] handle: &KeyObjectHandle,
  #[buffer] salt: JsBuffer,
  #[buffer] info: JsBuffer,
  #[number] okm_len: usize,
) -> Result<Uint8Array, HkdfError> {
  let handle = handle.clone();
  spawn_blocking(move || {
    let mut okm = vec![0u8; okm_len];
    hkdf_sync(&digest_algorithm, &handle, &salt, &info, &mut okm)?;
    Ok(okm.into())
  })
  .await?
}

#[op2]
pub fn op_node_dh_compute_secret(
  #[buffer] prime: JsBuffer,
  #[buffer] private_key: JsBuffer,
  #[buffer] their_public_key: JsBuffer,
) -> Uint8Array {
  let pubkey: BigUint = BigUint::from_bytes_be(their_public_key.as_ref());
  let privkey: BigUint = BigUint::from_bytes_be(private_key.as_ref());
  let primei: BigUint = BigUint::from_bytes_be(prime.as_ref());
  let shared_secret: BigUint = pubkey.modpow(&privkey, &primei);

  shared_secret.to_bytes_be().into()
}

#[op2(fast)]
#[number]
pub fn op_node_random_int(#[number] min: i64, #[number] max: i64) -> i64 {
  let mut rng = rand::thread_rng();
  // Uniform distribution is required to avoid Modulo Bias
  // https://en.wikipedia.org/wiki/Fisher–Yates_shuffle#Modulo_bias
  let dist = Uniform::from(min..max);

  dist.sample(&mut rng)
}

#[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
fn scrypt(
  password: StringOrBuffer,
  salt: StringOrBuffer,
  keylen: usize,
  cost: u64,
  block_size: u64,
  parallelization: u64,
  maxmem: usize,
  output_buffer: &mut [u8],
) -> Result<(), JsErrorBox> {
  assert!(
    output_buffer.len() >= keylen,
    "output_buffer too small for scrypt keylen",
  );
  let cost = u32::try_from(cost)
    .ok()
    .filter(|cost| *cost < 64)
    .ok_or_else(|| JsErrorBox::generic("Invalid scrypt param"))?;
  let n = 1u64
    .checked_shl(cost)
    .ok_or_else(|| JsErrorBox::generic("Invalid scrypt param"))?;

  // SAFETY:
  // - `password.as_ptr()`/`password.len()` describe a valid contiguous byte
  //   slice because `StringOrBuffer` dereferences to `[u8]`.
  // - `salt.as_ptr()`/`salt.len()` likewise describe a valid contiguous byte
  //   slice.
  // - `output_buffer.as_mut_ptr()` points to at least `keylen` writable bytes,
  //   enforced by the assertion above and by the callers allocating a buffer of
  //   that exact size.
  // - `n` is derived with `checked_shl`, so the `N` parameter passed to
  //   `EVP_PBE_scrypt` cannot overflow the shift.
  // - AWS-LC documents `EVP_PBE_scrypt` as thread-safe for independent inputs;
  //   this call does not alias mutable state across threads.
  let result = unsafe {
    aws_lc_sys::EVP_PBE_scrypt(
      password.as_ptr().cast(),
      password.len(),
      salt.as_ptr(),
      salt.len(),
      n,
      block_size,
      parallelization,
      maxmem,
      output_buffer.as_mut_ptr(),
      keylen,
    )
  };

  if result == 1 {
    Ok(())
  } else {
    Err(JsErrorBox::generic("scrypt key derivation failed"))
  }
}

#[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
#[op2]
pub fn op_node_scrypt_sync(
  #[serde] password: StringOrBuffer,
  #[serde] salt: StringOrBuffer,
  #[number] keylen: usize,
  #[number] cost: u64,
  #[number] block_size: u64,
  #[number] parallelization: u64,
  #[number] maxmem: usize,
  #[anybuffer] output_buffer: &mut [u8],
) -> Result<(), JsErrorBox> {
  scrypt(
    password,
    salt,
    keylen,
    cost,
    block_size,
    parallelization,
    maxmem,
    output_buffer,
  )
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum ScryptAsyncError {
  #[class(inherit)]
  #[error(transparent)]
  Join(#[from] tokio::task::JoinError),
  #[class(inherit)]
  #[error(transparent)]
  Other(JsErrorBox),
}

#[op2]
pub async fn op_node_scrypt_async(
  #[serde] password: StringOrBuffer,
  #[serde] salt: StringOrBuffer,
  #[number] keylen: usize,
  #[number] cost: u64,
  #[number] block_size: u64,
  #[number] parallelization: u64,
  #[number] maxmem: usize,
) -> Result<Uint8Array, ScryptAsyncError> {
  spawn_blocking(move || {
    let mut output_buffer = vec![0u8; keylen];

    scrypt(
      password,
      salt,
      keylen,
      cost,
      block_size,
      parallelization,
      maxmem,
      &mut output_buffer,
    )
    .map(|_| output_buffer.into())
    .map_err(ScryptAsyncError::Other)
  })
  .await?
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum EcdhEncodePubKey {
  #[class(type)]
  #[error("Invalid public key")]
  InvalidPublicKey,
  #[class(type)]
  #[error("Unsupported curve")]
  UnsupportedCurve,
  #[class(generic)]
  #[error(transparent)]
  Sec1(#[from] sec1::Error),
}

#[op2]
pub fn op_node_ecdh_encode_pubkey(
  #[string] curve: &str,
  #[buffer] pubkey: &[u8],
  compress: bool,
) -> Result<Uint8Array, EcdhEncodePubKey> {
  use elliptic_curve::sec1::FromEncodedPoint;

  match curve {
    "secp256k1" => {
      let pubkey =
        elliptic_curve::PublicKey::<k256::Secp256k1>::from_encoded_point(
          &elliptic_curve::sec1::EncodedPoint::<k256::Secp256k1>::from_bytes(
            pubkey,
          )?,
        );
      // CtOption does not expose its variants.
      if pubkey.is_none().into() {
        return Err(EcdhEncodePubKey::InvalidPublicKey);
      }

      let pubkey = pubkey.unwrap();

      Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into())
    }
    "prime256v1" | "secp256r1" => {
      let pubkey = elliptic_curve::PublicKey::<NistP256>::from_encoded_point(
        &elliptic_curve::sec1::EncodedPoint::<NistP256>::from_bytes(pubkey)?,
      );
      // CtOption does not expose its variants.
      if pubkey.is_none().into() {
        return Err(EcdhEncodePubKey::InvalidPublicKey);
      }

      let pubkey = pubkey.unwrap();

      Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into())
    }
    "secp384r1" => {
      let pubkey = elliptic_curve::PublicKey::<NistP384>::from_encoded_point(
        &elliptic_curve::sec1::EncodedPoint::<NistP384>::from_bytes(pubkey)?,
      );
      // CtOption does not expose its variants.
      if pubkey.is_none().into() {
        return Err(EcdhEncodePubKey::InvalidPublicKey);
      }

      let pubkey = pubkey.unwrap();

      Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into())
    }
    "secp521r1" => {
      let Some(pubkey): Option<elliptic_curve::PublicKey<NistP521>> =
        elliptic_curve::PublicKey::<NistP521>::from_encoded_point(
          &elliptic_curve::sec1::EncodedPoint::<NistP521>::from_bytes(pubkey)?,
        )
        .into()
      else {
        return Err(EcdhEncodePubKey::InvalidPublicKey);
      };

      Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into())
    }
    "secp224r1" => {
      let pubkey = elliptic_curve::PublicKey::<NistP224>::from_encoded_point(
        &elliptic_curve::sec1::EncodedPoint::<NistP224>::from_bytes(pubkey)?,
      );
      // CtOption does not expose its variants.
      if pubkey.is_none().into() {
        return Err(EcdhEncodePubKey::InvalidPublicKey);
      }

      let pubkey = pubkey.unwrap();

      Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into())
    }
    &_ => Err(EcdhEncodePubKey::UnsupportedCurve),
  }
}

#[op2(fast)]
pub fn op_node_ecdh_generate_keys(
  #[string] curve: &str,
  #[buffer] pubbuf: &mut [u8],
  #[buffer] privbuf: &mut [u8],
  #[string] format: &str,
) -> Result<(), JsErrorBox> {
  let mut rng = rand::thread_rng();
  let compress = format == "compressed";
  match curve {
    "secp256k1" => {
      let privkey =
        elliptic_curve::SecretKey::<k256::Secp256k1>::random(&mut rng);
      let pubkey = privkey.public_key();
      pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref());
      privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref());

      Ok(())
    }
    "prime256v1" | "secp256r1" => {
      let privkey = elliptic_curve::SecretKey::<NistP256>::random(&mut rng);
      let pubkey = privkey.public_key();
      pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref());
      privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref());

      Ok(())
    }
    "secp384r1" => {
      let privkey = elliptic_curve::SecretKey::<NistP384>::random(&mut rng);
      let pubkey = privkey.public_key();
      pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref());
      privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref());

      Ok(())
    }
    "secp521r1" => {
      let privkey = elliptic_curve::SecretKey::<NistP521>::random(&mut rng);
      let pubkey = privkey.public_key();
      pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref());
      privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref());

      Ok(())
    }
    "secp224r1" => {
      let privkey = elliptic_curve::SecretKey::<NistP224>::random(&mut rng);
      let pubkey = privkey.public_key();
      pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref());
      privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref());

      Ok(())
    }
    &_ => Err(JsErrorBox::type_error(format!(
      "Unsupported curve: {}",
      curve
    ))),
  }
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum EcdhError {
  #[class("ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY")]
  #[error("Public key is not valid for specified curve")]
  InvalidPublicKey,
  #[class(generic)]
  #[error("Private key is not valid for specified curve")]
  InvalidPrivateKey,
  #[class(type)]
  #[error("Unsupported curve")]
  UnsupportedCurve,
  #[class(generic)]
  #[error("Invalid key pair")]
  InvalidKeyPair,
}

#[op2]
pub fn op_node_ecdh_compute_secret(
  #[string] curve: &str,
  #[buffer] this_priv: Option<JsBuffer>,
  #[buffer] this_pub: Option<JsBuffer>,
  #[buffer] their_pub: &mut [u8],
  #[buffer] secret: &mut [u8],
) -> Result<(), EcdhError> {
  let this_priv = this_priv.ok_or(EcdhError::InvalidPrivateKey)?;
  match curve {
    "secp256k1" => {
      let their_public_key =
        elliptic_curve::PublicKey::<k256::Secp256k1>::from_sec1_bytes(
          their_pub,
        )
        .map_err(|_| EcdhError::InvalidPublicKey)?;
      let this_private_key =
        elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(&this_priv)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      if let Some(this_pub) = this_pub {
        let derived = this_private_key.public_key();
        let stored =
          elliptic_curve::PublicKey::<k256::Secp256k1>::from_sec1_bytes(
            &this_pub,
          )
          .map_err(|_| EcdhError::InvalidKeyPair)?;
        if derived != stored {
          return Err(EcdhError::InvalidKeyPair);
        }
      }
      let shared_secret = elliptic_curve::ecdh::diffie_hellman(
        this_private_key.to_nonzero_scalar(),
        their_public_key.as_affine(),
      );
      secret.copy_from_slice(shared_secret.raw_secret_bytes());
    }
    "prime256v1" | "secp256r1" => {
      let their_public_key =
        elliptic_curve::PublicKey::<NistP256>::from_sec1_bytes(their_pub)
          .map_err(|_| EcdhError::InvalidPublicKey)?;
      let this_private_key =
        elliptic_curve::SecretKey::<NistP256>::from_slice(&this_priv)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      if let Some(this_pub) = this_pub {
        let derived = this_private_key.public_key();
        let stored =
          elliptic_curve::PublicKey::<NistP256>::from_sec1_bytes(&this_pub)
            .map_err(|_| EcdhError::InvalidKeyPair)?;
        if derived != stored {
          return Err(EcdhError::InvalidKeyPair);
        }
      }
      let shared_secret = elliptic_curve::ecdh::diffie_hellman(
        this_private_key.to_nonzero_scalar(),
        their_public_key.as_affine(),
      );
      secret.copy_from_slice(shared_secret.raw_secret_bytes());
    }
    "secp384r1" => {
      let their_public_key =
        elliptic_curve::PublicKey::<NistP384>::from_sec1_bytes(their_pub)
          .map_err(|_| EcdhError::InvalidPublicKey)?;
      let this_private_key =
        elliptic_curve::SecretKey::<NistP384>::from_slice(&this_priv)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      if let Some(this_pub) = this_pub {
        let derived = this_private_key.public_key();
        let stored =
          elliptic_curve::PublicKey::<NistP384>::from_sec1_bytes(&this_pub)
            .map_err(|_| EcdhError::InvalidKeyPair)?;
        if derived != stored {
          return Err(EcdhError::InvalidKeyPair);
        }
      }
      let shared_secret = elliptic_curve::ecdh::diffie_hellman(
        this_private_key.to_nonzero_scalar(),
        their_public_key.as_affine(),
      );
      secret.copy_from_slice(shared_secret.raw_secret_bytes());
    }
    "secp521r1" => {
      let their_public_key =
        elliptic_curve::PublicKey::<NistP521>::from_sec1_bytes(their_pub)
          .map_err(|_| EcdhError::InvalidPublicKey)?;
      let this_private_key =
        elliptic_curve::SecretKey::<NistP521>::from_slice(&this_priv)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      if let Some(this_pub) = this_pub {
        let derived = this_private_key.public_key();
        let stored =
          elliptic_curve::PublicKey::<NistP521>::from_sec1_bytes(&this_pub)
            .map_err(|_| EcdhError::InvalidKeyPair)?;
        if derived != stored {
          return Err(EcdhError::InvalidKeyPair);
        }
      }
      let shared_secret = elliptic_curve::ecdh::diffie_hellman(
        this_private_key.to_nonzero_scalar(),
        their_public_key.as_affine(),
      );
      secret.copy_from_slice(shared_secret.raw_secret_bytes());
    }
    "secp224r1" => {
      let their_public_key =
        elliptic_curve::PublicKey::<NistP224>::from_sec1_bytes(their_pub)
          .map_err(|_| EcdhError::InvalidPublicKey)?;
      let this_private_key =
        elliptic_curve::SecretKey::<NistP224>::from_slice(&this_priv)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      if let Some(this_pub) = this_pub {
        let derived = this_private_key.public_key();
        let stored =
          elliptic_curve::PublicKey::<NistP224>::from_sec1_bytes(&this_pub)
            .map_err(|_| EcdhError::InvalidKeyPair)?;
        if derived != stored {
          return Err(EcdhError::InvalidKeyPair);
        }
      }
      let shared_secret = elliptic_curve::ecdh::diffie_hellman(
        this_private_key.to_nonzero_scalar(),
        their_public_key.as_affine(),
      );
      secret.copy_from_slice(shared_secret.raw_secret_bytes());
    }
    _ => return Err(EcdhError::UnsupportedCurve),
  }
  Ok(())
}

#[op2(fast)]
pub fn op_node_ecdh_compute_public_key(
  #[string] curve: &str,
  #[buffer] privkey: &[u8],
  #[buffer] pubkey: &mut [u8],
) -> Result<(), EcdhError> {
  match curve {
    "secp256k1" => {
      let this_private_key =
        elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(privkey)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      let public_key = this_private_key.public_key();
      pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref());
    }
    "prime256v1" | "secp256r1" => {
      let this_private_key =
        elliptic_curve::SecretKey::<NistP256>::from_slice(privkey)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      let public_key = this_private_key.public_key();
      pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref());
    }
    "secp384r1" => {
      let this_private_key =
        elliptic_curve::SecretKey::<NistP384>::from_slice(privkey)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      let public_key = this_private_key.public_key();
      pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref());
    }
    "secp521r1" => {
      let this_private_key =
        elliptic_curve::SecretKey::<NistP521>::from_slice(privkey)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      let public_key = this_private_key.public_key();
      pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref());
    }
    "secp224r1" => {
      let this_private_key =
        elliptic_curve::SecretKey::<NistP224>::from_slice(privkey)
          .map_err(|_| EcdhError::InvalidPrivateKey)?;
      let public_key = this_private_key.public_key();
      pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref());
    }
    _ => return Err(EcdhError::UnsupportedCurve),
  }
  Ok(())
}

#[op2(fast)]
pub fn op_node_ecdh_validate_private_key(
  #[string] curve: &str,
  #[buffer] privkey: &[u8],
) -> bool {
  match curve {
    "secp256k1" => {
      elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(privkey).is_ok()
    }
    "prime256v1" | "secp256r1" => {
      elliptic_curve::SecretKey::<NistP256>::from_slice(privkey).is_ok()
    }
    "secp384r1" => {
      elliptic_curve::SecretKey::<NistP384>::from_slice(privkey).is_ok()
    }
    "secp521r1" => {
      elliptic_curve::SecretKey::<NistP521>::from_slice(privkey).is_ok()
    }
    "secp224r1" => {
      elliptic_curve::SecretKey::<NistP224>::from_slice(privkey).is_ok()
    }
    _ => false,
  }
}

#[op2(fast)]
pub fn op_node_ecdh_validate_public_key(
  #[string] curve: &str,
  #[buffer] pubkey: &[u8],
) -> bool {
  use elliptic_curve::sec1::FromEncodedPoint;
  match curve {
    "secp256k1" => {
      let Ok(point) =
        elliptic_curve::sec1::EncodedPoint::<k256::Secp256k1>::from_bytes(
          pubkey,
        )
      else {
        return false;
      };
      let pk = elliptic_curve::PublicKey::<k256::Secp256k1>::from_encoded_point(
        &point,
      );
      bool::from(pk.is_some())
    }
    "prime256v1" | "secp256r1" => {
      let Ok(point) =
        elliptic_curve::sec1::EncodedPoint::<NistP256>::from_bytes(pubkey)
      else {
        return false;
      };
      let pk =
        elliptic_curve::PublicKey::<NistP256>::from_encoded_point(&point);
      bool::from(pk.is_some())
    }
    "secp384r1" => {
      let Ok(point) =
        elliptic_curve::sec1::EncodedPoint::<NistP384>::from_bytes(pubkey)
      else {
        return false;
      };
      let pk =
        elliptic_curve::PublicKey::<NistP384>::from_encoded_point(&point);
      bool::from(pk.is_some())
    }
    "secp521r1" => {
      let Ok(point) =
        elliptic_curve::sec1::EncodedPoint::<NistP521>::from_bytes(pubkey)
      else {
        return false;
      };
      let pk: Option<elliptic_curve::PublicKey<NistP521>> =
        elliptic_curve::PublicKey::<NistP521>::from_encoded_point(&point)
          .into();
      pk.is_some()
    }
    "secp224r1" => {
      let Ok(point) =
        elliptic_curve::sec1::EncodedPoint::<NistP224>::from_bytes(pubkey)
      else {
        return false;
      };
      let pk =
        elliptic_curve::PublicKey::<NistP224>::from_encoded_point(&point);
      bool::from(pk.is_some())
    }
    _ => false,
  }
}

/// Performs the structural portion of OpenSSL's DH_check(): verifies that
/// the generator is in the valid range (1 < g < p) and that the modulus is
/// at least 2. Returns a bitmask of error codes:
///   0x02 - generator is not suitable for p (g <= 1 or g >= p)
///   0x10 - p is degenerate (less than 2)
///
/// The full DH_check also tests p for primality (Miller-Rabin) and tests
/// whether (p-1)/2 is prime. Those checks are intentionally omitted: in a
/// debug build, Miller-Rabin on a 2048-bit modulus is slow enough to push
/// `createDiffieHellman` past test timeouts, and Node tests never expect
/// `verifyError === 0` for a non-prime large modulus.
#[op2(fast)]
pub fn op_node_dh_check(
  #[buffer] prime: &[u8],
  #[buffer] generator: &[u8],
) -> i32 {
  let p = BigInt::from_bytes_be(num_bigint::Sign::Plus, prime);
  let g = BigInt::from_bytes_be(num_bigint::Sign::Plus, generator);

  let mut code: i32 = 0;

  if p < BigInt::from(2) {
    code |= 0x10;
  }

  if g <= BigInt::from(1) || g >= p {
    code |= 0x02;
  }

  code
}

#[inline]
fn gen_prime(
  size: usize,
  safe: bool,
  add: Option<&[u8]>,
  rem: Option<&[u8]>,
) -> Result<Uint8Array, GeneratePrimeError> {
  if safe || add.is_some() || rem.is_some() {
    let prime = primes::Prime::generate_with_options(size, safe, add, rem)?;
    Ok(prime.0.to_bytes_be().into())
  } else {
    Ok(primes::Prime::generate(size).0.to_bytes_be().into())
  }
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum GeneratePrimeError {
  #[class("ERR_OUT_OF_RANGE")]
  #[error("invalid options.add")]
  InvalidAdd,
  #[class("ERR_OUT_OF_RANGE")]
  #[error("invalid options.rem")]
  InvalidRem,
  #[class("ERR_OUT_OF_RANGE")]
  #[error("prime generation failed: no suitable prime found in range")]
  OutOfRange,
  #[class(generic)]
  #[error(transparent)]
  JoinError(#[from] tokio::task::JoinError),
}

impl From<primes::GeneratePrimeError> for GeneratePrimeError {
  fn from(e: primes::GeneratePrimeError) -> Self {
    match e {
      primes::GeneratePrimeError::InvalidAdd => GeneratePrimeError::InvalidAdd,
      primes::GeneratePrimeError::InvalidRem => GeneratePrimeError::InvalidRem,
      primes::GeneratePrimeError::OutOfRange => GeneratePrimeError::OutOfRange,
    }
  }
}

#[op2]
pub fn op_node_gen_prime(
  #[number] size: usize,
  safe: bool,
  #[buffer] add: Option<JsBuffer>,
  #[buffer] rem: Option<JsBuffer>,
) -> Result<Uint8Array, GeneratePrimeError> {
  gen_prime(size, safe, add.as_deref(), rem.as_deref())
}

#[op2]
pub async fn op_node_gen_prime_async(
  #[number] size: usize,
  safe: bool,
  #[buffer] add: Option<JsBuffer>,
  #[buffer] rem: Option<JsBuffer>,
) -> Result<Uint8Array, GeneratePrimeError> {
  spawn_blocking(move || gen_prime(size, safe, add.as_deref(), rem.as_deref()))
    .await?
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum DiffieHellmanError {
  #[class(type)]
  #[error("Expected private key")]
  ExpectedPrivateKey,
  #[class(type)]
  #[error("Expected public key")]
  ExpectedPublicKey,
  #[class(generic)]
  #[error(
    "error:0308010C:digital envelope routines::mismatching domain parameters"
  )]
  MismatchingDomainParameters,
  #[class(generic)]
  #[error("error:030000A9:digital envelope routines::failed during derivation")]
  FailedDuringDerivation,
  #[class(type)]
  #[error("Unsupported key type for diffie hellman, or key type mismatch")]
  UnsupportedKeyTypeForDiffieHellmanOrKeyTypeMismatch,
}

#[op2]
#[buffer]
pub fn op_node_diffie_hellman(
  #[cppgc] private: &KeyObjectHandle,
  #[cppgc] public: &KeyObjectHandle,
) -> Result<Box<[u8]>, DiffieHellmanError> {
  let private = private
    .as_private_key()
    .ok_or(DiffieHellmanError::ExpectedPrivateKey)?;
  let public = public
    .as_public_key()
    .ok_or(DiffieHellmanError::ExpectedPublicKey)?;

  let res = match (private, &*public) {
    (
      AsymmetricPrivateKey::Ec(EcPrivateKey::P224(private)),
      AsymmetricPublicKey::Ec(EcPublicKey::P224(public)),
    ) => p224::ecdh::diffie_hellman(
      private.to_nonzero_scalar(),
      public.as_affine(),
    )
    .raw_secret_bytes()
    .to_vec()
    .into_boxed_slice(),
    (
      AsymmetricPrivateKey::Ec(EcPrivateKey::P256(private)),
      AsymmetricPublicKey::Ec(EcPublicKey::P256(public)),
    ) => p256::ecdh::diffie_hellman(
      private.to_nonzero_scalar(),
      public.as_affine(),
    )
    .raw_secret_bytes()
    .to_vec()
    .into_boxed_slice(),
    (
      AsymmetricPrivateKey::Ec(EcPrivateKey::P384(private)),
      AsymmetricPublicKey::Ec(EcPublicKey::P384(public)),
    ) => p384::ecdh::diffie_hellman(
      private.to_nonzero_scalar(),
      public.as_affine(),
    )
    .raw_secret_bytes()
    .to_vec()
    .into_boxed_slice(),
    (
      AsymmetricPrivateKey::Ec(EcPrivateKey::P521(private)),
      AsymmetricPublicKey::Ec(EcPublicKey::P521(public)),
    ) => p521::ecdh::diffie_hellman(
      private.to_nonzero_scalar(),
      public.as_affine(),
    )
    .raw_secret_bytes()
    .to_vec()
    .into_boxed_slice(),
    (
      AsymmetricPrivateKey::Ec(EcPrivateKey::Secp256k1(private)),
      AsymmetricPublicKey::Ec(EcPublicKey::Secp256k1(public)),
    ) => k256::ecdh::diffie_hellman(
      private.to_nonzero_scalar(),
      public.as_affine(),
    )
    .raw_secret_bytes()
    .to_vec()
    .into_boxed_slice(),
    (AsymmetricPrivateKey::Ec(_), AsymmetricPublicKey::Ec(_)) => {
      // Both EC keys but on different curves.
      return Err(DiffieHellmanError::MismatchingDomainParameters);
    }
    (
      AsymmetricPrivateKey::X25519(private),
      AsymmetricPublicKey::X25519(public),
    ) => {
      let shared = private.diffie_hellman(public);
      let bytes = shared.to_bytes();
      // Reject all-zero shared secrets (low-order points), matching OpenSSL.
      if bytes.iter().all(|b| *b == 0) {
        return Err(DiffieHellmanError::FailedDuringDerivation);
      }
      bytes.into_iter().collect()
    }
    (
      AsymmetricPrivateKey::X448(private),
      AsymmetricPublicKey::X448(public),
    ) => {
      let mut scalar_bytes = [0u8; 57];
      scalar_bytes[..56].copy_from_slice(&private[..56]);
      let scalar = ed448_goldilocks::EdwardsScalar::from_bytes_mod_order(
        &scalar_bytes.into(),
      );
      let point = ed448_goldilocks::MontgomeryPoint(*public);
      let shared = &point * &scalar;
      if shared.0.iter().all(|b| *b == 0) {
        return Err(DiffieHellmanError::FailedDuringDerivation);
      }
      shared.0.to_vec().into_boxed_slice()
    }
    (AsymmetricPrivateKey::Dh(private), AsymmetricPublicKey::Dh(public)) => {
      // Compare DH parameters by integer value, not byte encoding,
      // since different generation paths may produce different ASN.1
      // encodings of the same integer (e.g. with/without leading 0x00).
      let priv_prime = BigUint::from_bytes_be(private.params.prime.as_bytes());
      let pub_prime = BigUint::from_bytes_be(public.params.prime.as_bytes());
      let priv_base = BigUint::from_bytes_be(private.params.base.as_bytes());
      let pub_base = BigUint::from_bytes_be(public.params.base.as_bytes());
      if priv_prime != pub_prime || priv_base != pub_base {
        return Err(DiffieHellmanError::MismatchingDomainParameters);
      }

      // OSIP - Octet-String-to-Integer primitive
      let public_key = public.key.clone().into_vec();
      let pubkey = BigUint::from_bytes_be(&public_key);

      // Exponentiation (z = y^x mod p)
      let private_key = private.key.clone().into_vec();
      let private_key = BigUint::from_bytes_be(&private_key);
      let shared_secret = pubkey.modpow(&private_key, &priv_prime);

      // Pad to the byte length of the prime, matching OpenSSL's
      // DH_compute_key_padded behaviour used by EVP_PKEY_derive.
      let prime_len = priv_prime.bits().div_ceil(8);
      let secret_bytes = shared_secret.to_bytes_be();
      if secret_bytes.len() < prime_len {
        let mut padded = vec![0u8; prime_len];
        padded[prime_len - secret_bytes.len()..].copy_from_slice(&secret_bytes);
        padded.into_boxed_slice()
      } else {
        secret_bytes.into_boxed_slice()
      }
    }
    _ => {
      return Err(
        DiffieHellmanError::UnsupportedKeyTypeForDiffieHellmanOrKeyTypeMismatch,
      );
    }
  };

  Ok(res)
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[class(type)]
pub enum SignEd25519Error {
  #[error("Expected private key")]
  ExpectedPrivateKey,
  #[error("Expected Ed25519 private key")]
  ExpectedEd25519PrivateKey,
  #[error("Invalid Ed25519 private key")]
  InvalidEd25519PrivateKey,
}

#[op2(fast)]
pub fn op_node_sign_ed25519(
  #[cppgc] key: &KeyObjectHandle,
  #[buffer] data: &[u8],
  #[buffer] signature: &mut [u8],
) -> Result<(), SignEd25519Error> {
  let private = key
    .as_private_key()
    .ok_or(SignEd25519Error::ExpectedPrivateKey)?;

  let ed25519 = match private {
    AsymmetricPrivateKey::Ed25519(private) => private,
    _ => return Err(SignEd25519Error::ExpectedEd25519PrivateKey),
  };

  let pair = Ed25519KeyPair::from_seed_unchecked(ed25519.as_bytes().as_slice())
    .map_err(|_| SignEd25519Error::InvalidEd25519PrivateKey)?;
  signature.copy_from_slice(pair.sign(data).as_ref());

  Ok(())
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[class(type)]
pub enum VerifyEd25519Error {
  #[error("Expected public key")]
  ExpectedPublicKey,
  #[error("Expected Ed25519 public key")]
  ExpectedEd25519PublicKey,
}

#[op2(fast)]
pub fn op_node_verify_ed25519(
  #[cppgc] key: &KeyObjectHandle,
  #[buffer] data: &[u8],
  #[buffer] signature: &[u8],
) -> Result<bool, VerifyEd25519Error> {
  let public = key
    .as_public_key()
    .ok_or(VerifyEd25519Error::ExpectedPublicKey)?;

  let ed25519 = match &*public {
    AsymmetricPublicKey::Ed25519(public) => public,
    _ => return Err(VerifyEd25519Error::ExpectedEd25519PublicKey),
  };

  let verified = aws_lc_rs::signature::UnparsedPublicKey::new(
    &aws_lc_rs::signature::ED25519,
    ed25519.as_bytes().as_slice(),
  )
  .verify(data, signature)
  .is_ok();

  Ok(verified)
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[class(type)]
pub enum SignEd448Error {
  #[error("Expected private key")]
  ExpectedPrivateKey,
  #[error("Expected Ed448 private key")]
  ExpectedEd448PrivateKey,
}

#[op2(fast)]
pub fn op_node_sign_ed448(
  #[cppgc] key: &KeyObjectHandle,
  #[buffer] data: &[u8],
  #[buffer] signature: &mut [u8],
) -> Result<(), SignEd448Error> {
  let private = key
    .as_private_key()
    .ok_or(SignEd448Error::ExpectedPrivateKey)?;

  let ed448 = match private {
    AsymmetricPrivateKey::Ed448(private) => private,
    _ => return Err(SignEd448Error::ExpectedEd448PrivateKey),
  };

  let sig = ed448.sign_raw(data);
  signature.copy_from_slice(&sig.to_bytes());

  Ok(())
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[class(type)]
pub enum VerifyEd448Error {
  #[error("Expected public key")]
  ExpectedPublicKey,
  #[error("Expected Ed448 public key")]
  ExpectedEd448PublicKey,
}

#[op2(fast)]
pub fn op_node_verify_ed448(
  #[cppgc] key: &KeyObjectHandle,
  #[buffer] data: &[u8],
  #[buffer] signature: &[u8],
) -> Result<bool, VerifyEd448Error> {
  let public = key
    .as_public_key()
    .ok_or(VerifyEd448Error::ExpectedPublicKey)?;

  let ed448 = match &*public {
    AsymmetricPublicKey::Ed448(public) => public,
    _ => return Err(VerifyEd448Error::ExpectedEd448PublicKey),
  };

  let sig = match ed448_goldilocks::Signature::from_slice(signature) {
    Ok(sig) => sig,
    Err(_) => return Ok(false),
  };

  Ok(ed448.verify_raw(&sig, data).is_ok())
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum SpkacError {
  #[error("spkac is too large")]
  #[property("code" = "ERR_OUT_OF_RANGE")]
  #[class(range)]
  BufferOutOfRange,
}

#[op2(fast)]
pub fn op_node_verify_spkac(
  #[buffer] spkac: &[u8],
) -> Result<bool, SpkacError> {
  if spkac.len() > i32::MAX as usize {
    return Err(SpkacError::BufferOutOfRange);
  }

  Ok(deno_crypto_provider::spki::verify_spkac(spkac))
}

#[op2]
pub fn op_node_cert_export_public_key(
  #[buffer] spkac: &[u8],
) -> Result<Option<Uint8Array>, SpkacError> {
  if spkac.len() > i32::MAX as usize {
    return Err(SpkacError::BufferOutOfRange);
  }

  Ok(deno_crypto_provider::spki::export_public_key(spkac).map(Into::into))
}

#[op2]
pub fn op_node_cert_export_challenge(
  #[buffer] spkac: &[u8],
) -> Result<Option<Uint8Array>, SpkacError> {
  if spkac.len() > i32::MAX as usize {
    return Err(SpkacError::BufferOutOfRange);
  }

  Ok(deno_crypto_provider::spki::export_challenge(spkac).map(Into::into))
}
