// Copyright 2018-2025 the Deno authors. MIT license.
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::ToJsBuffer;
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 rand::Rng;
use rand::distributions::Distribution;
use rand::distributions::Uniform;
use rsa::Oaep;
use rsa::Pkcs1v15Encrypt;
use rsa::RsaPrivateKey;
use rsa::RsaPublicKey;
use rsa::pkcs8::DecodePrivateKey;
use rsa::pkcs8::DecodePublicKey;

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

use self::digest::match_fixed_digest_with_eager_block_buffer;

#[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(async)]
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(async)]
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]
#[serde]
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)]
  Utf8(#[from] std::str::Utf8Error),
  #[class(generic)]
  #[error(transparent)]
  Rsa(#[from] rsa::Error),
  #[class(type)]
  #[error("Unknown padding")]
  UnknownPadding,
}

#[op2]
#[serde]
pub fn op_node_private_encrypt(
  #[serde] key: StringOrBuffer,
  #[serde] msg: StringOrBuffer,
  #[smi] padding: u32,
) -> Result<ToJsBuffer, PrivateEncryptDecryptError> {
  let key = RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)?;

  let mut rng = rand::thread_rng();
  match padding {
    1 => Ok(
      key
        .as_ref()
        .encrypt(&mut rng, Pkcs1v15Encrypt, &msg)?
        .into(),
    ),
    4 => Ok(
      key
        .as_ref()
        .encrypt(&mut rng, Oaep::new::<sha1::Sha1>(), &msg)?
        .into(),
    ),
    _ => Err(PrivateEncryptDecryptError::UnknownPadding),
  }
}

#[op2]
#[serde]
pub fn op_node_private_decrypt(
  #[serde] key: StringOrBuffer,
  #[serde] msg: StringOrBuffer,
  #[smi] padding: u32,
) -> Result<ToJsBuffer, PrivateEncryptDecryptError> {
  let key = RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)?;

  match padding {
    1 => Ok(key.decrypt(Pkcs1v15Encrypt, &msg)?.into()),
    4 => Ok(key.decrypt(Oaep::new::<sha1::Sha1>(), &msg)?.into()),
    _ => Err(PrivateEncryptDecryptError::UnknownPadding),
  }
}

#[op2]
#[serde]
pub fn op_node_public_encrypt(
  #[serde] key: StringOrBuffer,
  #[serde] msg: StringOrBuffer,
  #[smi] padding: u32,
) -> Result<ToJsBuffer, PrivateEncryptDecryptError> {
  let key = RsaPublicKey::from_public_key_pem((&key).try_into()?)?;

  let mut rng = rand::thread_rng();
  match padding {
    1 => Ok(key.encrypt(&mut rng, Pkcs1v15Encrypt, &msg)?.into()),
    4 => Ok(
      key
        .encrypt(&mut rng, Oaep::new::<sha1::Sha1>(), &msg)?
        .into(),
    ),
    _ => Err(PrivateEncryptDecryptError::UnknownPadding),
  }
}

#[op2(fast)]
#[smi]
pub fn op_node_create_cipheriv(
  state: &mut OpState,
  #[string] algorithm: &str,
  #[buffer] key: &[u8],
  #[buffer] iv: &[u8],
) -> Result<u32, cipher::CipherContextError> {
  let context = cipher::CipherContext::new(algorithm, key, iv)?;
  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,
  };
  context.encrypt(input, output);
  true
}

#[op2]
#[serde]
pub fn op_node_cipheriv_final(
  state: &mut OpState,
  #[smi] rid: u32,
  auto_pad: bool,
  #[buffer] input: &[u8],
  #[anybuffer] output: &mut [u8],
) -> Result<Option<Vec<u8>>, 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)
}

#[op2]
#[buffer]
pub fn op_node_cipheriv_take(
  state: &mut OpState,
  #[smi] rid: u32,
) -> Result<Option<Vec<u8>>, 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())
}

#[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,
  };
  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<u32>,
  #[smi] dsa_signature_encoding: u32,
) -> Result<Box<[u8]>, sign::KeyObjectHandlePrehashedSignAndVerifyError> {
  handle.sign_prehashed(
    digest_type,
    digest,
    pss_salt_length,
    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<u32>,
  #[smi] dsa_signature_encoding: u32,
) -> Result<bool, sign::KeyObjectHandlePrehashedSignAndVerifyError> {
  handle.verify_prehashed(
    digest_type,
    digest,
    signature,
    pss_salt_length,
    dsa_signature_encoding,
  )
}

#[derive(Debug, PartialEq, Eq)]
#[allow(non_camel_case_types)]
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(async)]
#[serde]
pub async fn op_node_pbkdf2_async(
  #[anybuffer] password: JsBuffer,
  #[anybuffer] salt: JsBuffer,
  #[smi] iterations: u32,
  #[string] digest: String,
  #[number] keylen: usize,
) -> Result<ToJsBuffer, 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(async)]
#[serde]
pub async fn op_node_fill_random_async(#[smi] len: i32) -> ToJsBuffer {
  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(async)]
#[serde]
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<ToJsBuffer, 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]
#[serde]
pub fn op_node_dh_compute_secret(
  #[buffer] prime: JsBuffer,
  #[buffer] private_key: JsBuffer,
  #[buffer] their_public_key: JsBuffer,
) -> ToJsBuffer {
  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)]
fn scrypt(
  password: StringOrBuffer,
  salt: StringOrBuffer,
  keylen: u32,
  cost: u32,
  block_size: u32,
  parallelization: u32,
  _maxmem: u32,
  output_buffer: &mut [u8],
) -> Result<(), JsErrorBox> {
  // Construct Params
  let params = scrypt::Params::new(
    cost as u8,
    block_size,
    parallelization,
    keylen as usize,
  )
  .map_err(|_| JsErrorBox::generic("Invalid scrypt param"))?;

  // Call into scrypt
  let res = scrypt::scrypt(&password, &salt, &params, output_buffer);
  if res.is_ok() {
    Ok(())
  } else {
    // TODO(lev): key derivation failed, so what?
    Err(JsErrorBox::generic("scrypt key derivation failed"))
  }
}

#[allow(clippy::too_many_arguments)]
#[op2]
pub fn op_node_scrypt_sync(
  #[serde] password: StringOrBuffer,
  #[serde] salt: StringOrBuffer,
  #[smi] keylen: u32,
  #[smi] cost: u32,
  #[smi] block_size: u32,
  #[smi] parallelization: u32,
  #[smi] maxmem: u32,
  #[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(async)]
#[serde]
pub async fn op_node_scrypt_async(
  #[serde] password: StringOrBuffer,
  #[serde] salt: StringOrBuffer,
  #[smi] keylen: u32,
  #[smi] cost: u32,
  #[smi] block_size: u32,
  #[smi] parallelization: u32,
  #[smi] maxmem: u32,
) -> Result<ToJsBuffer, ScryptAsyncError> {
  spawn_blocking(move || {
    let mut output_buffer = vec![0u8; keylen as usize];

    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]
#[buffer]
pub fn op_node_ecdh_encode_pubkey(
  #[string] curve: &str,
  #[buffer] pubkey: &[u8],
  compress: bool,
) -> Result<Vec<u8>, 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())
    }
    "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())
    }
    "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())
    }
    "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())
    }
    &_ => 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(())
    }
    "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
    ))),
  }
}

#[op2]
pub fn op_node_ecdh_compute_secret(
  #[string] curve: &str,
  #[buffer] this_priv: Option<JsBuffer>,
  #[buffer] their_pub: &mut [u8],
  #[buffer] secret: &mut [u8],
) {
  match curve {
    "secp256k1" => {
      let their_public_key =
        elliptic_curve::PublicKey::<k256::Secp256k1>::from_sec1_bytes(
          their_pub,
        )
        .expect("bad public key");
      let this_private_key =
        elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(
          &this_priv.expect("must supply private key"),
        )
        .expect("bad private key");
      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)
          .expect("bad public key");
      let this_private_key = elliptic_curve::SecretKey::<NistP256>::from_slice(
        &this_priv.expect("must supply private key"),
      )
      .expect("bad private key");
      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)
          .expect("bad public key");
      let this_private_key = elliptic_curve::SecretKey::<NistP384>::from_slice(
        &this_priv.expect("must supply private key"),
      )
      .expect("bad private key");
      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)
          .expect("bad public key");
      let this_private_key = elliptic_curve::SecretKey::<NistP224>::from_slice(
        &this_priv.expect("must supply private key"),
      )
      .expect("bad private key");
      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());
    }
    &_ => todo!(),
  }
}

#[op2(fast)]
pub fn op_node_ecdh_compute_public_key(
  #[string] curve: &str,
  #[buffer] privkey: &[u8],
  #[buffer] pubkey: &mut [u8],
) {
  match curve {
    "secp256k1" => {
      let this_private_key =
        elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(privkey)
          .expect("bad private key");
      let public_key = this_private_key.public_key();
      pubkey.copy_from_slice(public_key.to_sec1_bytes().as_ref());
    }
    "prime256v1" | "secp256r1" => {
      let this_private_key =
        elliptic_curve::SecretKey::<NistP256>::from_slice(privkey)
          .expect("bad private key");
      let public_key = this_private_key.public_key();
      pubkey.copy_from_slice(public_key.to_sec1_bytes().as_ref());
    }
    "secp384r1" => {
      let this_private_key =
        elliptic_curve::SecretKey::<NistP384>::from_slice(privkey)
          .expect("bad private key");
      let public_key = this_private_key.public_key();
      pubkey.copy_from_slice(public_key.to_sec1_bytes().as_ref());
    }
    "secp224r1" => {
      let this_private_key =
        elliptic_curve::SecretKey::<NistP224>::from_slice(privkey)
          .expect("bad private key");
      let public_key = this_private_key.public_key();
      pubkey.copy_from_slice(public_key.to_sec1_bytes().as_ref());
    }
    &_ => todo!(),
  }
}

#[inline]
fn gen_prime(size: usize) -> ToJsBuffer {
  primes::Prime::generate(size).0.to_bytes_be().into()
}

#[op2]
#[serde]
pub fn op_node_gen_prime(#[number] size: usize) -> ToJsBuffer {
  gen_prime(size)
}

#[op2(async)]
#[serde]
pub async fn op_node_gen_prime_async(
  #[number] size: usize,
) -> Result<ToJsBuffer, tokio::task::JoinError> {
  spawn_blocking(move || gen_prime(size)).await
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[class(type)]
pub enum DiffieHellmanError {
  #[error("Expected private key")]
  ExpectedPrivateKey,
  #[error("Expected public key")]
  ExpectedPublicKey,
  #[error("DH parameters mismatch")]
  DhParametersMismatch,
  #[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::X25519(private),
        AsymmetricPublicKey::X25519(public),
      ) => private
        .diffie_hellman(public)
        .to_bytes()
        .into_iter()
        .collect(),
      (AsymmetricPrivateKey::Dh(private), AsymmetricPublicKey::Dh(public)) => {
        if private.params.prime != public.params.prime
          || private.params.base != public.params.base
        {
          return Err(DiffieHellmanError::DhParametersMismatch);
        }

        // 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 prime = BigUint::from_bytes_be(private.params.prime.as_bytes());
        let private_key = private.key.clone().into_vec();
        let private_key = BigUint::from_bytes_be(&private_key);
        let shared_secret = pubkey.modpow(&private_key, &prime);

        shared_secret.to_bytes_be().into()
      }
      _ => 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)]
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]
#[buffer]
pub fn op_node_cert_export_public_key(
  #[buffer] spkac: &[u8],
) -> Result<Option<Vec<u8>>, SpkacError> {
  if spkac.len() > i32::MAX as usize {
    return Err(SpkacError::BufferOutOfRange);
  }

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

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

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