#include "crypto/crypto_keys.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_dh.h"
#include "crypto/crypto_dsa.h"
#include "crypto/crypto_ec.h"
#include "crypto/crypto_pqc.h"
#include "crypto/crypto_rsa.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
#include "node_buffer.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "util-inl.h"
#include "v8.h"

namespace node {

using ncrypto::BignumPointer;
using ncrypto::BIOPointer;
using ncrypto::ECKeyPointer;
using ncrypto::ECPointPointer;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::MarkPopErrorOnReturn;
using ncrypto::PKCS8Pointer;
using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Isolate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Undefined;
using v8::Value;

namespace crypto {
namespace {
Maybe<EVPKeyPointer::AsymmetricKeyEncodingConfig> GetKeyFormatAndTypeFromJs(
    const FunctionCallbackInfo<Value>& args,
    unsigned int* offset,
    KeyEncodingContext context) {
  EVPKeyPointer::AsymmetricKeyEncodingConfig config;
  // During key pair generation, it is possible not to specify a key encoding,
  // which will lead to a key object being returned.
  if (args[*offset]->IsUndefined()) {
    CHECK_EQ(context, kKeyContextGenerate);
    CHECK(args[*offset + 1]->IsUndefined());
    config.output_key_object = true;
  } else {
    config.output_key_object = false;

    CHECK(args[*offset]->IsInt32());
    config.format = static_cast<EVPKeyPointer::PKFormatType>(
        args[*offset].As<Int32>()->Value());

    if (config.format == EVPKeyPointer::PKFormatType::RAW_PUBLIC ||
        config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
        config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
      // Raw formats use the type slot for ec_point_form (int) or null.
      if (args[*offset + 1]->IsInt32()) {
        config.ec_point_form = args[*offset + 1].As<Int32>()->Value();
      } else {
        CHECK(args[*offset + 1]->IsNullOrUndefined());
      }
    } else if (args[*offset + 1]->IsInt32()) {
      config.type = static_cast<EVPKeyPointer::PKEncodingType>(
          args[*offset + 1].As<Int32>()->Value());
    } else {
      CHECK((context == kKeyContextInput &&
             config.format == EVPKeyPointer::PKFormatType::PEM) ||
            (context == kKeyContextGenerate &&
             config.format == EVPKeyPointer::PKFormatType::JWK));
      CHECK(args[*offset + 1]->IsNullOrUndefined());
      config.type = EVPKeyPointer::PKEncodingType::PKCS1;
    }
  }

  *offset += 2;
  return Just(config);
}

MaybeLocal<Value> ToV8Value(
    Environment* env,
    const BIOPointer& bio,
    const EVPKeyPointer::AsymmetricKeyEncodingConfig& config) {
  if (!bio) {
    THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Invalid BIO pointer");
    return {};
  }
  BUF_MEM* bptr = bio;
  if (!bptr) {
    THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Unable to create BUF_MEM pointer");
    return {};
  }
  if (config.format == EVPKeyPointer::PKFormatType::PEM) {
    // PEM is an ASCII format, so we will return it as a string.
    return String::NewFromUtf8(
               env->isolate(), bptr->data, NewStringType::kNormal, bptr->length)
        .FromMaybe(Local<Value>());
  }

  CHECK_EQ(config.format, EVPKeyPointer::PKFormatType::DER);
  // DER is binary, return it as a buffer.
  return Buffer::Copy(env, bptr->data, bptr->length).FromMaybe(Local<Value>());
}

MaybeLocal<Value> WritePrivateKey(
    Environment* env,
    const EVPKeyPointer& pkey,
    const EVPKeyPointer::PrivateKeyEncodingConfig& config) {
  if (!pkey) return {};
  auto res = pkey.writePrivateKey(config);
  if (res) return ToV8Value(env, std::move(res.value), config);

  ThrowCryptoError(
      env, res.openssl_error.value_or(0), "Failed to encode private key");
  return MaybeLocal<Value>();
}

MaybeLocal<Value> WritePublicKey(
    Environment* env,
    const EVPKeyPointer& pkey,
    const EVPKeyPointer::PublicKeyEncodingConfig& config) {
  if (!pkey) return {};
  auto res = pkey.writePublicKey(config);
  if (res) return ToV8Value(env, res.value, config);

  ThrowCryptoError(
      env, res.openssl_error.value_or(0), "Failed to encode public key");
  return MaybeLocal<Value>();
}

bool ExportJWKSecretKey(Environment* env,
                        const KeyObjectData& key,
                        Local<Object> target) {
  CHECK_EQ(key.GetKeyType(), kKeyTypeSecret);

  Local<Value> raw;
  return StringBytes::Encode(env->isolate(),
                             key.GetSymmetricKey(),
                             key.GetSymmetricKeySize(),
                             BASE64URL)
             .ToLocal(&raw) &&
         target
             ->DefineOwnProperty(
                 env->context(), env->jwk_kty_string(), env->jwk_oct_string())
             .FromMaybe(false) &&
         target->DefineOwnProperty(env->context(), env->jwk_k_string(), raw)
             .FromMaybe(false);
}

KeyObjectData ImportJWKSecretKey(Environment* env, Local<Object> jwk) {
  Local<Value> key;
  if (!jwk->Get(env->context(), env->jwk_k_string()).ToLocal(&key) ||
      !key->IsString()) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK secret key format");
    return {};
  }

  static_assert(String::kMaxLength <= INT_MAX);
  return KeyObjectData::CreateSecret(
      ByteSource::FromEncodedString(env, key.As<String>()));
}

bool ExportJWKAsymmetricKey(Environment* env,
                            const KeyObjectData& key,
                            Local<Object> target,
                            bool handleRsaPss) {
  const int id = key.GetAsymmetricKey().id();
#if OPENSSL_WITH_PQC
  if (IsPqcKeyId(id)) return ExportJwkPqcKey(env, key, target);
#endif
  switch (id) {
    case EVP_PKEY_RSA_PSS: {
      if (handleRsaPss) return ExportJWKRsaKey(env, key, target);
      break;
    }
    case EVP_PKEY_RSA:
      return ExportJWKRsaKey(env, key, target);
    case EVP_PKEY_EC:
      return ExportJWKEcKey(env, key, target);
    case EVP_PKEY_ED25519:
    case EVP_PKEY_ED448:
    case EVP_PKEY_X25519:
    case EVP_PKEY_X448:
      return ExportJWKEdKey(env, key, target);
  }
  THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env);
  return false;
}

bool GetSecretKeyDetail(Environment* env,
                        const KeyObjectData& key,
                        Local<Object> target) {
  // For the secret key detail, all we care about is the length,
  // converted to bits.
  return target
      ->Set(env->context(),
            env->length_string(),
            Number::New(
                env->isolate(),
                static_cast<double>(key.GetSymmetricKeySize() * CHAR_BIT)))
      .IsJust();
}

bool GetAsymmetricKeyDetail(Environment* env,
                            const KeyObjectData& key,
                            Local<Object> target) {
  if (!key) {
    THROW_ERR_CRYPTO_OPERATION_FAILED(env);
    return false;
  }
  switch (key.GetAsymmetricKey().id()) {
    case EVP_PKEY_RSA:
      // Fall through
    case EVP_PKEY_RSA2:
      // Fall through
    case EVP_PKEY_RSA_PSS: return GetRsaKeyDetail(env, key, target);
    case EVP_PKEY_DSA: return GetDsaKeyDetail(env, key, target);
    case EVP_PKEY_EC: return GetEcKeyDetail(env, key, target);
    case EVP_PKEY_DH: return GetDhKeyDetail(env, key, target);
  }
  THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
  return false;
}

KeyObjectData TryParsePrivateKey(
    Environment* env,
    const EVPKeyPointer::PrivateKeyEncodingConfig& config,
    const ncrypto::Buffer<const unsigned char>& buffer) {
  auto res = EVPKeyPointer::TryParsePrivateKey(config, buffer);
  if (res) {
    return KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate,
                                           std::move(res.value));
  }

  if (res.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) {
    THROW_ERR_MISSING_PASSPHRASE(env, "Passphrase required for encrypted key");
  } else {
    ThrowCryptoError(
        env, res.openssl_error.value_or(0), "Failed to read private key");
  }
  return {};
}

bool ExportJWKInner(Environment* env,
                    const KeyObjectData& key,
                    Local<Value> result,
                    bool handleRsaPss) {
  return key.GetKeyType() == kKeyTypeSecret
             ? ExportJWKSecretKey(env, key, result.As<Object>())
             : ExportJWKAsymmetricKey(
                   env, key, result.As<Object>(), handleRsaPss);
}

int GetNidFromName(const char* name) {
  static constexpr struct {
    const char* name;
    int nid;
  } kNameToNid[] = {
      {"Ed25519", EVP_PKEY_ED25519},
      {"Ed448", EVP_PKEY_ED448},
      {"X25519", EVP_PKEY_X25519},
      {"X448", EVP_PKEY_X448},
  };
  for (const auto& entry : kNameToNid) {
    if (StringEqualNoCase(name, entry.name)) return entry.nid;
  }
#if OPENSSL_WITH_PQC
  return GetPqcNidFromName(name);
#else
  return NID_undef;
#endif
}

bool IsUnavailablePqcKeyType(Environment* env, Local<String> key_type) {
  return key_type->StringEquals(env->crypto_ml_dsa_44_string()) ||
         key_type->StringEquals(env->crypto_ml_dsa_65_string()) ||
         key_type->StringEquals(env->crypto_ml_dsa_87_string()) ||
         key_type->StringEquals(env->crypto_ml_kem_512_string()) ||
         key_type->StringEquals(env->crypto_ml_kem_768_string()) ||
         key_type->StringEquals(env->crypto_ml_kem_1024_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_sha2_128f_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_sha2_128s_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_sha2_192f_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_sha2_192s_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_sha2_256f_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_sha2_256s_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_shake_128f_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_shake_128s_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_shake_192f_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_shake_192s_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_shake_256f_string()) ||
         key_type->StringEquals(env->crypto_slh_dsa_shake_256s_string());
}

bool IsUnsupportedRawKeyType(Environment* env, Local<String> key_type) {
  return key_type->StringEquals(env->crypto_rsa_string()) ||
         key_type->StringEquals(env->crypto_rsa_pss_string()) ||
         key_type->StringEquals(env->crypto_dsa_string()) ||
         key_type->StringEquals(env->crypto_dh_string());
}

void ValidateRawKeyImportFormat(Environment* env,
                                Local<String> key_type,
                                const char* key_type_name,
                                int id,
                                EVPKeyPointer::PKFormatType format) {
  auto validate_raw_format =
      [&](EVPKeyPointer::PKFormatType expected_private_format) {
        if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC ||
            format == expected_private_format) {
          return;
        }
        THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
      };

  if (key_type->StringEquals(env->crypto_ec_string())) {
    return validate_raw_format(EVPKeyPointer::PKFormatType::RAW_PRIVATE);
  }

  switch (id) {
    case EVP_PKEY_X25519:
    case EVP_PKEY_X448:
    case EVP_PKEY_ED25519:
    case EVP_PKEY_ED448:
      return validate_raw_format(EVPKeyPointer::PKFormatType::RAW_PRIVATE);
    default:
      break;
  }

#if OPENSSL_WITH_PQC
  if (IsPqcSeedKeyId(id)) {
    return validate_raw_format(EVPKeyPointer::PKFormatType::RAW_SEED);
  }
  if (IsPqcRawPrivateKeyId(id)) {
    return validate_raw_format(EVPKeyPointer::PKFormatType::RAW_PRIVATE);
  }
#endif

  if (IsUnavailablePqcKeyType(env, key_type)) {
    THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type");
    return;
  }

  if (IsUnsupportedRawKeyType(env, key_type)) {
    THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
    return;
  }

  THROW_ERR_INVALID_ARG_VALUE(
      env, "Invalid asymmetricKeyType: %s", key_type_name);
}
}  // namespace

bool KeyObjectData::ToEncodedPublicKey(
    Environment* env,
    const EVPKeyPointer::PublicKeyEncodingConfig& config,
    Local<Value>* out) {
  CHECK(key_type_ != KeyType::kKeyTypeSecret);
  if (config.output_key_object) {
    // Note that this has the downside of containing sensitive data of the
    // private key.
    return KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePublic))
        .ToLocal(out);
  } else if (config.format == EVPKeyPointer::PKFormatType::JWK) {
    *out = Object::New(env->isolate());
    return ExportJWKInner(
        env, addRefWithType(KeyType::kKeyTypePublic), *out, false);
  } else if (config.format == EVPKeyPointer::PKFormatType::RAW_PUBLIC) {
    Mutex::ScopedLock lock(mutex());
    const auto& pkey = GetAsymmetricKey();
    if (pkey.id() == EVP_PKEY_EC) {
      ECKeyPointer ec_key(pkey);
      if (!ec_key) {
        THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
        return false;
      }
      auto form = static_cast<point_conversion_form_t>(config.ec_point_form);
      const auto group = ec_key.getGroup();
      const auto point = ec_key.getPublicKey();
      return ECPointToBuffer(env, group, point, form).ToLocal(out);
    }
    const int id = pkey.id();
    bool is_raw_supported = id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448 ||
                            id == EVP_PKEY_X25519 || id == EVP_PKEY_X448;
#if OPENSSL_WITH_PQC
    is_raw_supported = is_raw_supported || IsPqcKeyId(id);
#endif
    if (!is_raw_supported) {
      THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
      return false;
    }
    auto raw_data = pkey.rawPublicKey();
    if (!raw_data) {
      THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw public key");
      return false;
    }
    return Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
        .ToLocal(out);
  }

  return WritePublicKey(env, GetAsymmetricKey(), config).ToLocal(out);
}

bool KeyObjectData::ToEncodedPrivateKey(
    Environment* env,
    const EVPKeyPointer::PrivateKeyEncodingConfig& config,
    Local<Value>* out) {
  CHECK(key_type_ != KeyType::kKeyTypeSecret);
  if (config.output_key_object) {
    return KeyObjectHandle::Create(env,
                                   addRefWithType(KeyType::kKeyTypePrivate))
        .ToLocal(out);
  } else if (config.format == EVPKeyPointer::PKFormatType::JWK) {
    *out = Object::New(env->isolate());
    return ExportJWKInner(
        env, addRefWithType(KeyType::kKeyTypePrivate), *out, false);
  } else if (config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE) {
    Mutex::ScopedLock lock(mutex());
    const auto& pkey = GetAsymmetricKey();
    if (pkey.id() == EVP_PKEY_EC) {
      ECKeyPointer ec_key(pkey);
      if (!ec_key) {
        THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
        return false;
      }
      const BIGNUM* private_key = ec_key.getPrivateKey();
      if (private_key == nullptr) {
        THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get EC private key");
        return false;
      }
      const auto group = ec_key.getGroup();
      auto order = BignumPointer::New();
      if (!order || !EC_GROUP_get_order(group, order.get(), nullptr)) {
        THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                          "Failed to export EC private key");
        return false;
      }
      auto buf = BignumPointer::EncodePadded(private_key, order.byteLength());
      if (!buf) {
        THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                          "Failed to export EC private key");
        return false;
      }
      return Buffer::Copy(env, buf.get<const char>(), buf.size()).ToLocal(out);
    }
    const int id = pkey.id();
    bool is_raw_supported = id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448 ||
                            id == EVP_PKEY_X25519 || id == EVP_PKEY_X448;
#if OPENSSL_WITH_PQC
    is_raw_supported = is_raw_supported || IsPqcRawPrivateKeyId(id);
#endif
    if (!is_raw_supported) {
      THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
      return false;
    }
    auto raw_data = pkey.rawPrivateKey();
    if (!raw_data) {
      THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw private key");
      return false;
    }
    return Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
        .ToLocal(out);
  } else if (config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
#if OPENSSL_WITH_PQC
    Mutex::ScopedLock lock(mutex());
    const auto& pkey = GetAsymmetricKey();
    if (!IsPqcSeedKeyId(pkey.id())) {
      THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
      return false;
    }
    auto raw_data = pkey.rawSeed();
    if (!raw_data) {
      THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed");
      return false;
    }
    return Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
        .ToLocal(out);
#else
    THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
    return false;
#endif
  }

  return WritePrivateKey(env, GetAsymmetricKey(), config).ToLocal(out);
}

Maybe<EVPKeyPointer::PrivateKeyEncodingConfig>
KeyObjectData::GetPrivateKeyEncodingFromJs(
    const FunctionCallbackInfo<Value>& args,
    unsigned int* offset,
    KeyEncodingContext context) {
  Environment* env = Environment::GetCurrent(args);

  EVPKeyPointer::PrivateKeyEncodingConfig config;
  if (!GetKeyFormatAndTypeFromJs(args, offset, context).To(&config)) {
    return Nothing<EVPKeyPointer::PrivateKeyEncodingConfig>();
  }

  if (config.output_key_object) {
    if (context != kKeyContextInput)
      (*offset)++;
  } else if (config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
             config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
    // Raw formats don't support encryption. Still consume the arg offsets.
    if (context != kKeyContextInput) {
      CHECK(args[*offset]->IsNullOrUndefined());
      (*offset)++;
    }
    CHECK(args[*offset]->IsNullOrUndefined());
  } else {
    bool needs_passphrase = false;
    if (context != kKeyContextInput) {
      if (args[*offset]->IsString()) {
        Utf8Value cipher_name(env->isolate(), args[*offset]);
        config.cipher = ncrypto::getCipherByName(*cipher_name);
        if (config.cipher == nullptr) {
          THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
          return Nothing<EVPKeyPointer::PrivateKeyEncodingConfig>();
        }
        needs_passphrase = true;
      } else {
        CHECK(args[*offset]->IsNullOrUndefined());
        config.cipher = nullptr;
      }
      (*offset)++;
    }

    if (IsAnyBufferSource(args[*offset])) {
      CHECK_IMPLIES(context != kKeyContextInput, config.cipher != nullptr);
      ArrayBufferOrViewContents<char> passphrase(args[*offset]);
      if (!passphrase.CheckSizeInt32()) [[unlikely]] {
        THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big");
        return Nothing<EVPKeyPointer::PrivateKeyEncodingConfig>();
      }
      config.passphrase = passphrase.ToDataPointer();
    } else {
      CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase);
    }
  }

  (*offset)++;
  return Just<EVPKeyPointer::PrivateKeyEncodingConfig>(std::move(config));
}

Maybe<EVPKeyPointer::PublicKeyEncodingConfig>
KeyObjectData::GetPublicKeyEncodingFromJs(
    const FunctionCallbackInfo<Value>& args,
    unsigned int* offset,
    KeyEncodingContext context) {
  return GetKeyFormatAndTypeFromJs(args, offset, context);
}

// Shared helper for importing raw asymmetric keys. Called from
// ImportRawKeyFromArgs.
static KeyObjectData ImportRawKey(Environment* env,
                                  const unsigned char* key_data,
                                  size_t key_data_len,
                                  EVPKeyPointer::PKFormatType format,
                                  Local<String> key_type,
                                  const char* key_type_name,
                                  const char* named_curve,
                                  KeyType target_type) {
  auto throw_invalid = [&]() {
    if (!env->isolate()->HasPendingException()) {
      THROW_ERR_INVALID_ARG_VALUE(env, "Invalid key data");
    }
  };

  const int id = GetNidFromName(key_type_name);
  ValidateRawKeyImportFormat(env, key_type, key_type_name, id, format);
  if (env->isolate()->HasPendingException()) {
    return {};
  }

  // EC keys
  if (key_type->StringEquals(env->crypto_ec_string())) {
    int curve_nid = ncrypto::Ec::GetCurveIdFromName(named_curve);
    if (curve_nid == NID_undef) {
      THROW_ERR_CRYPTO_INVALID_CURVE(env);
      return {};
    }
    auto eckey = ECKeyPointer::NewByCurveName(curve_nid);
    if (!eckey) {
      throw_invalid();
      return {};
    }
    if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC) {
      const auto group = eckey.getGroup();
      auto pub = ECPointPointer::New(group);
      if (!pub) {
        throw_invalid();
        return {};
      }
      ncrypto::Buffer<const unsigned char> buffer{
          .data = key_data,
          .len = key_data_len,
      };
      if (!pub.setFromBuffer(buffer, group) || !eckey.setPublicKey(pub)) {
        throw_invalid();
        return {};
      }
    } else {
      const auto group = eckey.getGroup();
      auto order = BignumPointer::New();
      CHECK(order);
      CHECK(EC_GROUP_get_order(group, order.get(), nullptr));
      if (key_data_len != order.byteLength()) {
        throw_invalid();
        return {};
      }
      BignumPointer priv_bn(key_data, key_data_len);
      if (!priv_bn || !eckey.setPrivateKey(priv_bn)) {
        throw_invalid();
        return {};
      }
      auto pub_point = ECPointPointer::New(group);
      if (!pub_point || !pub_point.mul(group, priv_bn.get()) ||
          !eckey.setPublicKey(pub_point)) {
        throw_invalid();
        return {};
      }
    }
    auto pkey = EVPKeyPointer::New();
    if (!pkey.assign(eckey)) {
      throw_invalid();
      return {};
    }
#if NCRYPTO_USE_LEGACY_KEY_TYPES
    eckey.release();
#endif
    return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey));
  }

  typedef EVPKeyPointer (*new_key_fn)(
      int, const ncrypto::Buffer<const unsigned char>&);
  new_key_fn fn = nullptr;
  switch (id) {
    case EVP_PKEY_X25519:
    case EVP_PKEY_X448:
    case EVP_PKEY_ED25519:
    case EVP_PKEY_ED448:
      fn = target_type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate
                                          : EVPKeyPointer::NewRawPublic;
      break;
    default:
#if OPENSSL_WITH_PQC
      if (IsPqcKeyId(id)) {
        if (target_type == kKeyTypePrivate) {
          fn = IsPqcSeedKeyId(id) ? EVPKeyPointer::NewRawSeed
                                  : EVPKeyPointer::NewRawPrivate;
        } else {
          fn = EVPKeyPointer::NewRawPublic;
        }
      }
#endif
      break;
  }

  if (fn != nullptr) {
    auto pkey = fn(id,
                   ncrypto::Buffer<const unsigned char>{
                       .data = key_data,
                       .len = key_data_len,
                   });
    if (!pkey) {
      throw_invalid();
      return {};
    }
    return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey));
  }

  return {};
}

// Shared helper for importing a JWK asymmetric key.  Extracts kty from the
// JWK object and dispatches to the appropriate importer.
static KeyObjectData ImportJWKFromArgs(Environment* env, Local<Object> jwk) {
  Local<Value> kty;
  if (!jwk->Get(env->context(), env->jwk_kty_string()).ToLocal(&kty) ||
      !kty->IsString()) {
    THROW_ERR_CRYPTO_INVALID_JWK(env);
    return {};
  }
  Utf8Value kty_string(env->isolate(), kty);
  if (*kty_string == std::string_view("RSA")) {
    return ImportJWKRsaKey(env, jwk);
  } else if (*kty_string == std::string_view("EC")) {
    return ImportJWKEcKey(env, jwk);
  } else if (*kty_string == std::string_view("OKP")) {
    return ImportJWKEdKey(env, jwk);
  } else if (*kty_string == std::string_view("AKP")) {
#if OPENSSL_WITH_PQC
    return ImportJWKPqcKey(env, jwk);
#else
    THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type");
    return {};
#endif
  }

  THROW_ERR_CRYPTO_INVALID_JWK(
      env, "%s is not a supported JWK key type", *kty_string);
  return {};
}

// Shared helper for importing raw asymmetric keys from positional args.
// args layout: [... offset+0: buffer, offset+1: formatInt,
//               offset+2: asymmetricKeyType, offset+3: passphrase,
//               offset+4: namedCurve]
static KeyObjectData ImportRawKeyFromArgs(
    const FunctionCallbackInfo<Value>& args, unsigned int offset) {
  Environment* env = Environment::GetCurrent(args);

  auto format = static_cast<EVPKeyPointer::PKFormatType>(
      args[offset + 1].As<Int32>()->Value());
  KeyType type = (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC)
                     ? kKeyTypePublic
                     : kKeyTypePrivate;

  ArrayBufferOrViewContents<unsigned char> key_data(args[offset]);
  if (!key_data.CheckSizeInt32()) [[unlikely]] {
    THROW_ERR_OUT_OF_RANGE(env, "keyData is too big");
    return {};
  }

  CHECK(args[offset + 2]->IsString());
  Local<String> key_type = args[offset + 2].As<String>();
  Utf8Value key_type_name(env->isolate(), key_type);

  DCHECK_IMPLIES(key_type->StringEquals(env->crypto_ec_string()),
                 args[offset + 4]->IsString());
  Utf8Value curve(env->isolate(),
                  args[offset + 4]->IsString() ? args[offset + 4].As<String>()
                                               : String::Empty(env->isolate()));

  return ImportRawKey(env,
                      key_data.data(),
                      key_data.size(),
                      format,
                      key_type,
                      *key_type_name,
                      *curve,
                      type);
}

KeyObjectData KeyObjectData::GetPrivateKeyFromJs(
    const v8::FunctionCallbackInfo<v8::Value>& args,
    unsigned int* offset,
    bool allow_key_object) {
  Environment* env = Environment::GetCurrent(args);

  // JWK format: data is a JS Object (not buffer), format int is JWK.
  if (args[*offset]->IsObject() && !IsAnyBufferSource(args[*offset]) &&
      args[*offset + 1]->IsInt32()) {
    auto format = static_cast<EVPKeyPointer::PKFormatType>(
        args[*offset + 1].As<Int32>()->Value());
    if (format == EVPKeyPointer::PKFormatType::JWK) {
      auto data = ImportJWKFromArgs(env, args[*offset].As<Object>());
      *offset += 5;
      return data;
    }
  }

  if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) {
    // Raw format: buffer + raw format int.
    if (args[*offset + 1]->IsInt32()) {
      auto format = static_cast<EVPKeyPointer::PKFormatType>(
          args[*offset + 1].As<Int32>()->Value());
      if (format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
          format == EVPKeyPointer::PKFormatType::RAW_SEED) {
        auto data = ImportRawKeyFromArgs(args, *offset);
        *offset += 5;
        return data;
      }
    }

    auto key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);

    EVPKeyPointer::PrivateKeyEncodingConfig config;
    if (!GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput)
             .To(&config)) {
      return {};
    }

    // Skip the namedCurve argument (only used by raw format imports).
    (*offset)++;

    return TryParsePrivateKey(
        env,
        config,
        ncrypto::Buffer<const unsigned char>{
            .data = reinterpret_cast<const unsigned char*>(key.data()),
            .len = key.size(),
        });
  }

  CHECK(args[*offset]->IsObject() && allow_key_object);
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As<Object>(), KeyObjectData());
  CHECK_EQ(key->Data().GetKeyType(), kKeyTypePrivate);
  (*offset) += 5;
  return key->Data().addRef();
}

KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs(
    const FunctionCallbackInfo<Value>& args, unsigned int* offset) {
  Environment* env = Environment::GetCurrent(args);

  // JWK format: data is a JS Object (not buffer), format int is JWK.
  if (args[*offset]->IsObject() && !IsAnyBufferSource(args[*offset]) &&
      args[*offset + 1]->IsInt32()) {
    auto format = static_cast<EVPKeyPointer::PKFormatType>(
        args[*offset + 1].As<Int32>()->Value());
    if (format == EVPKeyPointer::PKFormatType::JWK) {
      auto data = ImportJWKFromArgs(env, args[*offset].As<Object>());
      *offset += 5;
      return data;
    }
  }

  if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) {
    // Raw format: buffer + raw format int.
    if (args[*offset + 1]->IsInt32()) {
      auto format = static_cast<EVPKeyPointer::PKFormatType>(
          args[*offset + 1].As<Int32>()->Value());
      if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC ||
          format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
          format == EVPKeyPointer::PKFormatType::RAW_SEED) {
        auto data = ImportRawKeyFromArgs(args, *offset);
        *offset += 5;
        return data;
      }
    }

    ArrayBufferOrViewContents<char> data(args[(*offset)++]);
    if (!data.CheckSizeInt32()) [[unlikely]] {
      THROW_ERR_OUT_OF_RANGE(env, "keyData is too big");
      return {};
    }

    EVPKeyPointer::PrivateKeyEncodingConfig config;
    if (!KeyObjectData::GetPrivateKeyEncodingFromJs(
             args, offset, kKeyContextInput)
             .To(&config)) {
      return {};
    }

    // Skip the namedCurve argument (only used by raw format imports).
    (*offset)++;

    ncrypto::Buffer<const unsigned char> buffer = {
        .data = reinterpret_cast<const unsigned char*>(data.data()),
        .len = data.size(),
    };

    if (config.format == EVPKeyPointer::PKFormatType::PEM) {
      // For PEM, we can easily determine whether it is a public or private key
      // by looking for the respective PEM tags.
      auto res = EVPKeyPointer::TryParsePublicKeyPEM(buffer);
      if (res) {
        return CreateAsymmetric(kKeyTypePublic, std::move(res.value));
      }

      if (res.error.value() == EVPKeyPointer::PKParseError::NOT_RECOGNIZED) {
        return TryParsePrivateKey(env, config, buffer);
      }
      ThrowCryptoError(
          env, res.openssl_error.value_or(0), "Failed to read asymmetric key");
      return {};
    }

    // For DER, the type determines how to parse it. SPKI, PKCS#8 and SEC1 are
    // easy, but PKCS#1 can be a public key or a private key.
    static const auto is_public = [](const auto& config,
                                     const auto& buffer) -> bool {
      switch (config.type) {
        case EVPKeyPointer::PKEncodingType::PKCS1:
          return !EVPKeyPointer::IsRSAPrivateKey(buffer);
        case EVPKeyPointer::PKEncodingType::SPKI:
          return true;
        case EVPKeyPointer::PKEncodingType::PKCS8:
          return false;
        case EVPKeyPointer::PKEncodingType::SEC1:
          return false;
        default:
          UNREACHABLE("Invalid key encoding type");
      }
    };

    if (is_public(config, buffer)) {
      auto res = EVPKeyPointer::TryParsePublicKey(config, buffer);
      if (res) {
        return CreateAsymmetric(KeyType::kKeyTypePublic, std::move(res.value));
      }

      ThrowCryptoError(
          env, res.openssl_error.value_or(0), "Failed to read asymmetric key");
      return {};
    }

    return TryParsePrivateKey(env, config, buffer);
  }

  CHECK(args[*offset]->IsObject());
  KeyObjectHandle* key =
      BaseObject::Unwrap<KeyObjectHandle>(args[*offset].As<Object>());
  CHECK_NOT_NULL(key);
  CHECK_NE(key->Data().GetKeyType(), kKeyTypeSecret);
  (*offset) += 5;
  return key->Data().addRef();
}

KeyObjectData KeyObjectData::GetParsedKey(KeyType type,
                                          Environment* env,
                                          EVPKeyPointer&& pkey,
                                          ParseKeyResult ret,
                                          const char* default_msg) {
  MarkPopErrorOnReturn mark_pop_error_on_return;
  switch (ret) {
    case ParseKeyResult::kParseKeyOk: {
      return CreateAsymmetric(type, std::move(pkey));
    }
    case ParseKeyResult::kParseKeyNeedPassphrase: {
      THROW_ERR_MISSING_PASSPHRASE(env,
                                   "Passphrase required for encrypted key");
      return {};
    }
    default: {
      ThrowCryptoError(env, mark_pop_error_on_return.peekError(), default_msg);
      return {};
    }
  }
}

KeyObjectData::KeyObjectData(std::nullptr_t)
    : key_type_(KeyType::kKeyTypeSecret) {}

KeyObjectData::KeyObjectData(ByteSource symmetric_key)
    : key_type_(KeyType::kKeyTypeSecret),
      mutex_(std::make_shared<Mutex>()),
      data_(std::make_shared<Data>(std::move(symmetric_key))) {}

KeyObjectData::KeyObjectData(KeyType type, EVPKeyPointer&& pkey)
    : key_type_(type),
      mutex_(std::make_shared<Mutex>()),
      data_(std::make_shared<Data>(std::move(pkey))) {}

void KeyObjectData::Data::MemoryInfo(MemoryTracker* tracker) const {
  if (asymmetric_key) {
    tracker->TrackFieldWithSize("key",
                                kSizeOf_EVP_PKEY +
                                    asymmetric_key.rawPublicKeySize() +
                                    asymmetric_key.rawPrivateKeySize());
  } else {
    tracker->TraitTrackInline(symmetric_key, "symmetric_key");
  }
}

void KeyObjectData::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("data", data_);
}

Mutex& KeyObjectData::mutex() const {
  if (!mutex_) mutex_ = std::make_shared<Mutex>();
  return *mutex_.get();
}

KeyObjectData KeyObjectData::CreateSecret(ByteSource key) {
  return KeyObjectData(std::move(key));
}

KeyObjectData KeyObjectData::CreateAsymmetric(KeyType key_type,
                                              EVPKeyPointer&& pkey) {
  CHECK(pkey);
  return KeyObjectData(key_type, std::move(pkey));
}

KeyType KeyObjectData::GetKeyType() const {
  CHECK(data_);
  return key_type_;
}

const EVPKeyPointer& KeyObjectData::GetAsymmetricKey() const {
  CHECK_NE(key_type_, kKeyTypeSecret);
  CHECK(data_);
  return data_->asymmetric_key;
}

const char* KeyObjectData::GetSymmetricKey() const {
  CHECK_EQ(key_type_, kKeyTypeSecret);
  CHECK(data_);
  return data_->symmetric_key.data<char>();
}

size_t KeyObjectData::GetSymmetricKeySize() const {
  CHECK_EQ(key_type_, kKeyTypeSecret);
  CHECK(data_);
  return data_->symmetric_key.size();
}

bool KeyObjectHandle::HasInstance(Environment* env, Local<Value> value) {
  auto t = env->crypto_key_object_handle_constructor();
  return !t.IsEmpty() && t->HasInstance(value);
}

Local<Function> KeyObjectHandle::Initialize(Environment* env) {
  auto templ = env->crypto_key_object_handle_constructor();
  if (templ.IsEmpty()) {
    Isolate* isolate = env->isolate();
    templ = NewFunctionTemplate(isolate, New);
    templ->InstanceTemplate()->SetInternalFieldCount(
        KeyObjectHandle::kInternalFieldCount);

    SetProtoMethod(isolate, templ, "init", Init);
    SetProtoMethodNoSideEffect(isolate, templ, "getKeyType", GetKeyType);
    SetProtoMethodNoSideEffect(
        isolate, templ, "getSymmetricKeySize", GetSymmetricKeySize);
    SetProtoMethodNoSideEffect(
        isolate, templ, "getAsymmetricKeyType", GetAsymmetricKeyType);
    SetProtoMethodNoSideEffect(
        isolate, templ, "checkEcKeyData", CheckEcKeyData);
    SetProtoMethod(isolate, templ, "export", Export);
    SetProtoMethod(isolate, templ, "exportJwk", ExportJWK);
    SetProtoMethodNoSideEffect(isolate, templ, "rawPublicKey", RawPublicKey);
    SetProtoMethodNoSideEffect(isolate, templ, "rawPrivateKey", RawPrivateKey);
    SetProtoMethodNoSideEffect(isolate, templ, "rawSeed", RawSeed);
    SetProtoMethodNoSideEffect(
        isolate, templ, "exportECPublicRaw", ExportECPublicRaw);
    SetProtoMethodNoSideEffect(
        isolate, templ, "exportECPrivateRaw", ExportECPrivateRaw);
    SetProtoMethod(isolate, templ, "keyDetail", GetKeyDetail);
    SetProtoMethod(isolate, templ, "equals", Equals);

    env->set_crypto_key_object_handle_constructor(templ);
  }
  return templ->GetFunction(env->context()).ToLocalChecked();
}

void KeyObjectHandle::RegisterExternalReferences(
    ExternalReferenceRegistry* registry) {
  registry->Register(New);
  registry->Register(Init);
  registry->Register(GetKeyType);
  registry->Register(GetSymmetricKeySize);
  registry->Register(GetAsymmetricKeyType);
  registry->Register(CheckEcKeyData);
  registry->Register(Export);
  registry->Register(ExportJWK);
  registry->Register(RawPublicKey);
  registry->Register(RawPrivateKey);
  registry->Register(RawSeed);
  registry->Register(ExportECPublicRaw);
  registry->Register(ExportECPrivateRaw);
  registry->Register(GetKeyDetail);
  registry->Register(Equals);
}

MaybeLocal<Object> KeyObjectHandle::Create(Environment* env,
                                           const KeyObjectData& data) {
  Local<Object> obj;
  Local<Function> ctor = KeyObjectHandle::Initialize(env);
  CHECK(!env->crypto_key_object_handle_constructor().IsEmpty());
  if (!ctor->NewInstance(env->context(), 0, nullptr).ToLocal(&obj)) {
    return {};
  }

  KeyObjectHandle* key = Unwrap<KeyObjectHandle>(obj);
  CHECK_NOT_NULL(key);
  key->data_ = data.addRef();
  return obj;
}

const KeyObjectData& KeyObjectHandle::Data() {
  return data_;
}

void KeyObjectHandle::New(const FunctionCallbackInfo<Value>& args) {
  CHECK(args.IsConstructCall());
  Environment* env = Environment::GetCurrent(args);
  new KeyObjectHandle(env, args.This());
}

KeyObjectHandle::KeyObjectHandle(Environment* env,
                                 Local<Object> wrap)
    : BaseObject(env, wrap) {
  MakeWeak();
}

void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
  MarkPopErrorOnReturn mark_pop_error_on_return;

  Environment* env = Environment::GetCurrent(args);
  CHECK(args[0]->IsInt32());
  KeyType type = static_cast<KeyType>(args[0].As<Uint32>()->Value());

  unsigned int offset;

  switch (type) {
  case kKeyTypeSecret: {
    if (args.Length() == 5 && args[2]->IsInt32()) {
      auto format = static_cast<EVPKeyPointer::PKFormatType>(
          args[2].As<Int32>()->Value());
      if (format == EVPKeyPointer::PKFormatType::JWK) {
        CHECK(args[1]->IsObject());
        key->data_ = ImportJWKSecretKey(env, args[1].As<Object>());
        break;
      }
    }
    CHECK_EQ(args.Length(), 2);
    ArrayBufferOrViewContents<char> buf(args[1]);
    key->data_ = KeyObjectData::CreateSecret(buf.ToCopy());
    break;
  }
  case kKeyTypePublic:
  case kKeyTypePrivate: {
    CHECK_EQ(args.Length(), 6);

    // Check if this is a raw or JWK format import:
    // args: [keyType, buffer/object, formatInt, typeString/null,
    //        passphrase/null, namedCurve/null]
    if (args[2]->IsInt32()) {
      auto format = static_cast<EVPKeyPointer::PKFormatType>(
          args[2].As<Int32>()->Value());
      if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC ||
          format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
          format == EVPKeyPointer::PKFormatType::RAW_SEED) {
        auto data = ImportRawKeyFromArgs(args, 1);
        if (!data) return;
        if (type == kKeyTypePublic && data.GetKeyType() == kKeyTypePrivate) {
          key->data_ = data.addRefWithType(kKeyTypePublic);
        } else {
          key->data_ = std::move(data);
        }
        break;
      }
      if (format == EVPKeyPointer::PKFormatType::JWK) {
        CHECK(args[1]->IsObject());
        key->data_ = ImportJWKFromArgs(env, args[1].As<Object>());
        if (!key->data_) return;
        if (type == kKeyTypePublic &&
            key->data_.GetKeyType() == kKeyTypePrivate) {
          key->data_ = key->data_.addRefWithType(kKeyTypePublic);
        } else if (type == kKeyTypePrivate &&
                   key->data_.GetKeyType() == kKeyTypePublic) {
          THROW_ERR_CRYPTO_INVALID_JWK(
              env, "JWK does not contain private key material");
          return;
        }
        args.GetReturnValue().Set(key->data_.GetKeyType());
        break;
      }
    }

    offset = 1;
    if (type == kKeyTypePublic) {
      auto data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset);
      if (!data) return;
      key->data_ = data.addRefWithType(kKeyTypePublic);
    } else {
      if (auto data =
              KeyObjectData::GetPrivateKeyFromJs(args, &offset, false)) {
        key->data_ = std::move(data);
      }
    }
    break;
  }
  default:
    UNREACHABLE();
  }
}

void KeyObjectHandle::GetKeyType(const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  args.GetReturnValue().Set(
      Uint32::NewFromUnsigned(args.GetIsolate(), key->Data().GetKeyType()));
}

void KeyObjectHandle::Equals(const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* self_handle;
  KeyObjectHandle* arg_handle;
  ASSIGN_OR_RETURN_UNWRAP(&self_handle, args.This());
  ASSIGN_OR_RETURN_UNWRAP(&arg_handle, args[0].As<Object>());
  const auto& key = self_handle->Data();
  const auto& key2 = arg_handle->Data();

  KeyType key_type = key.GetKeyType();
  CHECK_EQ(key_type, key2.GetKeyType());

  bool ret;
  switch (key_type) {
    case kKeyTypeSecret: {
      size_t size = key.GetSymmetricKeySize();
      if (size == key2.GetSymmetricKeySize()) {
        ret = CRYPTO_memcmp(
                  key.GetSymmetricKey(), key2.GetSymmetricKey(), size) == 0;
      } else {
        ret = false;
      }
      break;
    }
    case kKeyTypePublic:
      // Fall through
    case kKeyTypePrivate: {
      EVP_PKEY* pkey = key.GetAsymmetricKey().get();
      EVP_PKEY* pkey2 = key2.GetAsymmetricKey().get();
#if OPENSSL_VERSION_MAJOR >= 3
      int ok = EVP_PKEY_eq(pkey, pkey2);
#else
      int ok = EVP_PKEY_cmp(pkey, pkey2);
#endif
      if (ok == -2) {
        Environment* env = Environment::GetCurrent(args);
        return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
      }
      ret = ok == 1;
      break;
    }
    default:
      UNREACHABLE("unsupported key type");
  }

  args.GetReturnValue().Set(ret);
}

void KeyObjectHandle::GetKeyDetail(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  CHECK(args[0]->IsObject());

  const auto& data = key->Data();

  if (data.GetKeyType() == kKeyTypeSecret) {
    if (GetSecretKeyDetail(env, data, args[0].As<Object>())) [[likely]] {
      args.GetReturnValue().Set(args[0]);
    }
    return;
  }

  if (GetAsymmetricKeyDetail(env, data, args[0].As<Object>())) [[likely]] {
    args.GetReturnValue().Set(args[0]);
  }
}

Local<Value> KeyObjectHandle::GetAsymmetricKeyType() const {
  switch (data_.GetAsymmetricKey().id()) {
    case EVP_PKEY_RSA:
      return env()->crypto_rsa_string();
    case EVP_PKEY_RSA_PSS:
      return env()->crypto_rsa_pss_string();
    case EVP_PKEY_DSA:
      return env()->crypto_dsa_string();
    case EVP_PKEY_DH:
      return env()->crypto_dh_string();
    case EVP_PKEY_EC:
      return env()->crypto_ec_string();
    case EVP_PKEY_ED25519:
      return env()->crypto_ed25519_string();
    case EVP_PKEY_ED448:
      return env()->crypto_ed448_string();
    case EVP_PKEY_X25519:
      return env()->crypto_x25519_string();
    case EVP_PKEY_X448:
      return env()->crypto_x448_string();
#if OPENSSL_WITH_PQC
    default:
      return GetPqcAsymmetricKeyType(env(), data_.GetAsymmetricKey().id());
#else
    default:
      return Undefined(env()->isolate());
#endif
  }
}

void KeyObjectHandle::GetAsymmetricKeyType(
    const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  args.GetReturnValue().Set(key->GetAsymmetricKeyType());
}

bool KeyObjectHandle::CheckEcKeyData() const {
  MarkPopErrorOnReturn mark_pop_error_on_return;

  const auto& key = data_.GetAsymmetricKey();
  EVPKeyCtxPointer ctx = key.newCtx();
  CHECK(ctx);
  CHECK_EQ(key.id(), EVP_PKEY_EC);

  return data_.GetKeyType() == kKeyTypePrivate ? ctx.privateCheck()
                                               : ctx.publicCheck();
}

void KeyObjectHandle::CheckEcKeyData(const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  args.GetReturnValue().Set(key->CheckEcKeyData());
}

void KeyObjectHandle::GetSymmetricKeySize(
    const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
  args.GetReturnValue().Set(
      static_cast<uint32_t>(key->Data().GetSymmetricKeySize()));
}

void KeyObjectHandle::Export(const FunctionCallbackInfo<Value>& args) {
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  KeyType type = key->Data().GetKeyType();
  unsigned int offset = 0;

  Local<Value> result;
  if (type == kKeyTypeSecret) {
    if (key->ExportSecretKey().ToLocal(&result)) [[likely]] {
      args.GetReturnValue().Set(result);
    }
    return;
  }

  if (type == kKeyTypePublic) {
    EVPKeyPointer::PublicKeyEncodingConfig config;
    if (!KeyObjectData::GetPublicKeyEncodingFromJs(
             args, &offset, kKeyContextExport)
             .To(&config)) {
      return;
    }
    CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
    if (key->ExportPublicKey(config).ToLocal(&result)) [[likely]] {
      args.GetReturnValue().Set(result);
    }
    return;
  }

  CHECK_EQ(type, kKeyTypePrivate);
  EVPKeyPointer::PrivateKeyEncodingConfig config;
  if (!KeyObjectData::GetPrivateKeyEncodingFromJs(
           args, &offset, kKeyContextExport)
           .To(&config)) {
    return;
  }
  CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
  if (key->ExportPrivateKey(config).ToLocal(&result)) [[likely]] {
    args.GetReturnValue().Set(result);
  }
}

MaybeLocal<Value> KeyObjectHandle::ExportSecretKey() const {
  return Buffer::Copy(
             env(), data_.GetSymmetricKey(), data_.GetSymmetricKeySize())
      .FromMaybe(Local<Value>());
}

MaybeLocal<Value> KeyObjectHandle::ExportPublicKey(
    const EVPKeyPointer::PublicKeyEncodingConfig& config) const {
  return WritePublicKey(env(), data_.GetAsymmetricKey(), config);
}

MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey(
    const EVPKeyPointer::PrivateKeyEncodingConfig& config) const {
  return WritePrivateKey(env(), data_.GetAsymmetricKey(), config);
}

void KeyObjectHandle::RawPublicKey(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  const KeyObjectData& data = key->Data();
  CHECK_NE(data.GetKeyType(), kKeyTypeSecret);

  Mutex::ScopedLock lock(data.mutex());
  const auto& pkey = data.GetAsymmetricKey();

  const int id = pkey.id();
  bool is_raw_supported = id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448 ||
                          id == EVP_PKEY_X25519 || id == EVP_PKEY_X448;
#if OPENSSL_WITH_PQC
  is_raw_supported = is_raw_supported || IsPqcKeyId(id);
#endif
  if (!is_raw_supported) {
    return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
  }

  auto raw_data = pkey.rawPublicKey();
  if (!raw_data) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                             "Failed to get raw public key");
  }

  args.GetReturnValue().Set(
      Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
          .FromMaybe(Local<Value>()));
}

void KeyObjectHandle::RawPrivateKey(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  const KeyObjectData& data = key->Data();
  CHECK_EQ(data.GetKeyType(), kKeyTypePrivate);

  Mutex::ScopedLock lock(data.mutex());
  const auto& pkey = data.GetAsymmetricKey();

  const int id = pkey.id();
  bool is_raw_supported = id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448 ||
                          id == EVP_PKEY_X25519 || id == EVP_PKEY_X448;
#if OPENSSL_WITH_PQC
  is_raw_supported = is_raw_supported || IsPqcRawPrivateKeyId(id);
#endif
  if (!is_raw_supported) {
    return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
  }

  auto raw_data = pkey.rawPrivateKey();
  if (!raw_data) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                             "Failed to get raw private key");
  }

  args.GetReturnValue().Set(
      Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
          .FromMaybe(Local<Value>()));
}

void KeyObjectHandle::ExportECPublicRaw(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  const KeyObjectData& data = key->Data();
  CHECK_NE(data.GetKeyType(), kKeyTypeSecret);

  Mutex::ScopedLock lock(data.mutex());
  const auto& m_pkey = data.GetAsymmetricKey();
  if (m_pkey.id() != EVP_PKEY_EC) {
    return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
  }

  ECKeyPointer ec_key(m_pkey);
  if (!ec_key) {
    return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
  }

  CHECK(args[0]->IsInt32());
  auto form =
      static_cast<point_conversion_form_t>(args[0].As<Int32>()->Value());

  const auto group = ec_key.getGroup();
  const auto point = ec_key.getPublicKey();

  Local<Object> buf;
  if (!ECPointToBuffer(env, group, point, form).ToLocal(&buf)) return;

  args.GetReturnValue().Set(buf);
}

void KeyObjectHandle::ExportECPrivateRaw(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  const KeyObjectData& data = key->Data();
  CHECK_EQ(data.GetKeyType(), kKeyTypePrivate);

  Mutex::ScopedLock lock(data.mutex());
  const auto& m_pkey = data.GetAsymmetricKey();
  if (m_pkey.id() != EVP_PKEY_EC) {
    return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
  }

  ECKeyPointer ec_key(m_pkey);
  if (!ec_key) {
    return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
  }

  const BIGNUM* private_key = ec_key.getPrivateKey();
  if (private_key == nullptr) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                             "Failed to get EC private key");
  }

  const auto group = ec_key.getGroup();
  auto order = BignumPointer::New();
  if (!order || !EC_GROUP_get_order(group, order.get(), nullptr)) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                             "Failed to export EC private key");
  }

  auto buf = BignumPointer::EncodePadded(private_key, order.byteLength());
  if (!buf) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                             "Failed to export EC private key");
  }

  args.GetReturnValue().Set(Buffer::Copy(env, buf.get<const char>(), buf.size())
                                .FromMaybe(Local<Value>()));
}

void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo<v8::Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  const KeyObjectData& data = key->Data();
  CHECK_EQ(data.GetKeyType(), kKeyTypePrivate);

#if OPENSSL_WITH_PQC
  Mutex::ScopedLock lock(data.mutex());
  const auto& pkey = data.GetAsymmetricKey();

  if (!IsPqcSeedKeyId(pkey.id())) {
    return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
  }

  auto raw_data = pkey.rawSeed();
  if (!raw_data) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed");
  }

  args.GetReturnValue().Set(
      Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
          .FromMaybe(Local<Value>()));
#else
  return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
#endif
}

void KeyObjectHandle::ExportJWK(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  KeyObjectHandle* key;
  ASSIGN_OR_RETURN_UNWRAP(&key, args.This());

  CHECK(args[0]->IsObject());
  CHECK(args[1]->IsBoolean());

  if (ExportJWKInner(env, key->Data(), args[0], args[1]->IsTrue())) {
    args.GetReturnValue().Set(args[0]);
  }
}

void NativeKeyObject::Initialize(Environment* env, Local<Object> target) {
  SetMethod(env->context(),
            target,
            "createNativeKeyObjectClass",
            NativeKeyObject::CreateNativeKeyObjectClass);
  SetMethod(
      env->context(), target, "getKeyObjectSlots", NativeKeyObject::GetSlots);
}

void NativeKeyObject::RegisterExternalReferences(
    ExternalReferenceRegistry* registry) {
  registry->Register(NativeKeyObject::CreateNativeKeyObjectClass);
  registry->Register(NativeKeyObject::GetSlots);
  registry->Register(NativeKeyObject::New);
}

bool NativeKeyObject::HasInstance(Environment* env, Local<Value> value) {
  auto t = env->crypto_key_object_constructor_template();
  return !t.IsEmpty() && t->HasInstance(value);
}

void NativeKeyObject::New(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK_EQ(args.Length(), 1);
  CHECK(KeyObjectHandle::HasInstance(env, args[0]));
  KeyObjectHandle* handle = Unwrap<KeyObjectHandle>(args[0].As<Object>());
  CHECK_NOT_NULL(handle);
  new NativeKeyObject(env, args.This(), handle->Data());
}

void NativeKeyObject::CreateNativeKeyObjectClass(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Isolate* isolate = env->isolate();

  CHECK_EQ(args.Length(), 1);
  Local<Value> callback = args[0];
  CHECK(callback->IsFunction());

  Local<FunctionTemplate> t =
      NewFunctionTemplate(isolate, NativeKeyObject::New);
  t->InstanceTemplate()->SetInternalFieldCount(
      NativeKeyObject::kInternalFieldCount);
  CHECK(env->crypto_key_object_constructor_template().IsEmpty());
  env->set_crypto_key_object_constructor_template(t);

  Local<Value> ctor;
  if (!t->GetFunction(env->context()).ToLocal(&ctor))
    return;

  Local<Value> recv = Undefined(env->isolate());
  Local<Value> ret_v;
  if (!callback.As<Function>()->Call(
          env->context(), recv, 1, &ctor).ToLocal(&ret_v)) {
    return;
  }
  Local<Array> ret = ret_v.As<Array>();
  if (!ret->Get(env->context(), 1).ToLocal(&ctor)) return;
  env->set_crypto_key_object_secret_constructor(ctor.As<Function>());
  if (!ret->Get(env->context(), 2).ToLocal(&ctor)) return;
  env->set_crypto_key_object_public_constructor(ctor.As<Function>());
  if (!ret->Get(env->context(), 3).ToLocal(&ctor)) return;
  env->set_crypto_key_object_private_constructor(ctor.As<Function>());
  args.GetReturnValue().Set(ret);
}

// Returns the key's native hidden slot tuple as a single Array:
// [type enum, handle]. JS-side helpers call this once per key to prime
// a per-instance cache; derived metadata is appended lazily from JS by
// calling methods on the returned KeyObjectHandle.
void NativeKeyObject::GetSlots(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK_EQ(args.Length(), 1);
  if (!HasInstance(env, args[0])) {
    THROW_ERR_INVALID_THIS(env, "Value of \"this\" must be of type KeyObject");
    return;
  }

  NativeKeyObject* native = Unwrap<NativeKeyObject>(args[0].As<Object>());
  CHECK_NOT_NULL(native);

  Local<Object> handle;
  if (!KeyObjectHandle::Create(env, native->handle_data_).ToLocal(&handle)) {
    return;
  }

  Isolate* isolate = env->isolate();
  Local<Value> slots[] = {
      Uint32::NewFromUnsigned(isolate, native->handle_data_.GetKeyType()),
      handle,
  };
  args.GetReturnValue().Set(Array::New(isolate, slots, arraysize(slots)));
}

BaseObjectPtr<BaseObject> NativeKeyObject::KeyObjectTransferData::Deserialize(
        Environment* env,
        Local<Context> context,
        std::unique_ptr<worker::TransferData> self) {
  if (context != env->context()) {
    THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
    return {};
  }

  Local<Value> handle;
  if (!KeyObjectHandle::Create(env, data_).ToLocal(&handle))
    return {};

  Local<Function> key_ctor;
  Local<Value> arg = FIXED_ONE_BYTE_STRING(env->isolate(),
                                           "internal/crypto/keys");
  if (env->builtin_module_require()
          ->Call(context, Null(env->isolate()), 1, &arg)
          .IsEmpty()) {
    return {};
  }
  switch (data_.GetKeyType()) {
    case kKeyTypeSecret:
      key_ctor = env->crypto_key_object_secret_constructor();
      break;
    case kKeyTypePublic:
      key_ctor = env->crypto_key_object_public_constructor();
      break;
    case kKeyTypePrivate:
      key_ctor = env->crypto_key_object_private_constructor();
      break;
    default:
      UNREACHABLE();
  }

  Local<Value> key;
  if (!key_ctor->NewInstance(context, 1, &handle).ToLocal(&key))
    return {};

  return BaseObjectPtr<BaseObject>(Unwrap<NativeKeyObject>(key.As<Object>()));
}

BaseObject::TransferMode NativeKeyObject::GetTransferMode() const {
  return BaseObject::TransferMode::kCloneable;
}

std::unique_ptr<worker::TransferData> NativeKeyObject::CloneForMessaging()
    const {
  return std::make_unique<KeyObjectTransferData>(handle_data_);
}

WebCryptoKeyExportStatus PKEY_SPKI_Export(const KeyObjectData& key_data,
                                          ByteSource* out) {
  CHECK_EQ(key_data.GetKeyType(), kKeyTypePublic);
  Mutex::ScopedLock lock(key_data.mutex());
  auto bio = key_data.GetAsymmetricKey().derPublicKey();
  if (!bio) return WebCryptoKeyExportStatus::FAILED;
  *out = ByteSource::FromBIO(bio);
  return WebCryptoKeyExportStatus::OK;
}

WebCryptoKeyExportStatus PKEY_PKCS8_Export(const KeyObjectData& key_data,
                                           ByteSource* out) {
  CHECK_EQ(key_data.GetKeyType(), kKeyTypePrivate);
  Mutex::ScopedLock lock(key_data.mutex());
  const auto& m_pkey = key_data.GetAsymmetricKey();

  auto bio = BIOPointer::NewMem();
  CHECK(bio);
  PKCS8Pointer p8inf(EVP_PKEY2PKCS8(m_pkey.get()));
  if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio.get(), p8inf.get()))
    return WebCryptoKeyExportStatus::FAILED;

  *out = ByteSource::FromBIO(bio);
  return WebCryptoKeyExportStatus::OK;
}

void NativeCryptoKey::Initialize(Environment* env, Local<Object> target) {
  SetMethod(env->context(),
            target,
            "createCryptoKeyClass",
            NativeCryptoKey::CreateCryptoKeyClass);
  SetMethod(
      env->context(), target, "getCryptoKeySlots", NativeCryptoKey::GetSlots);
}

void NativeCryptoKey::RegisterExternalReferences(
    ExternalReferenceRegistry* registry) {
  registry->Register(NativeCryptoKey::CreateCryptoKeyClass);
  registry->Register(NativeCryptoKey::GetSlots);
  registry->Register(NativeCryptoKey::New);
}

namespace {
// Verifies that `value` is a `NativeCryptoKey` by checking whether it
// was constructed from the Environment's `NativeCryptoKey` template.
bool IsNativeCryptoKey(Environment* env, Local<Value> value) {
  auto t = env->crypto_cryptokey_constructor_template();
  return !t.IsEmpty() && t->HasInstance(value);
}
}  // namespace

bool NativeCryptoKey::HasInstance(Environment* env, Local<Value> value) {
  return IsNativeCryptoKey(env, value);
}

MaybeLocal<Value> NativeCryptoKey::Create(Environment* env,
                                          const KeyObjectData& data,
                                          Local<Value> algorithm,
                                          uint32_t usages_mask,
                                          bool extractable) {
  Local<Context> context = env->context();
  Isolate* isolate = env->isolate();
  CHECK(algorithm->IsObject());

  Local<Object> handle;
  if (!KeyObjectHandle::Create(env, data).ToLocal(&handle)) return {};

  if (env->crypto_internal_cryptokey_constructor().IsEmpty()) {
    Local<Value> arg = FIXED_ONE_BYTE_STRING(isolate, "internal/crypto/keys");
    if (env->builtin_module_require()
            ->Call(context, Null(isolate), 1, &arg)
            .IsEmpty()) {
      return {};
    }
  }

  Local<Function> cryptokey_ctor = env->crypto_internal_cryptokey_constructor();
  CHECK(!cryptokey_ctor.IsEmpty());
  Local<Value> ctor_args[] = {
      handle,
      algorithm,
      Uint32::NewFromUnsigned(isolate, usages_mask),
      Boolean::New(isolate, extractable),
  };
  return cryptokey_ctor->NewInstance(context, arraysize(ctor_args), ctor_args);
}

void NativeCryptoKey::New(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK_EQ(args.Length(), 4);
  // args[0] is a KeyObjectHandle; we keep its KeyObjectData directly.
  // args[1] is the algorithm dictionary object.
  // args[2] is the usages mask.
  // args[3] is the extractable boolean.
  //
  // args[1] is undefined only when called from
  // CryptoKeyTransferData::Deserialize for a partially-initialized
  // CryptoKey: algorithm/usages mask/extractable get filled in afterwards
  // by FinalizeTransferRead before any JS can see the object.
  //
  // This constructor is not exposed to user JS - the public CryptoKey
  // class throws from its constructor and InternalCryptoKey is kept
  // in a module-closure.
  CHECK(KeyObjectHandle::HasInstance(env, args[0]));
  KeyObjectHandle* handle = Unwrap<KeyObjectHandle>(args[0].As<Object>());
  CHECK_NOT_NULL(handle);

  auto* native = new NativeCryptoKey(env, args.This(), handle->Data());

  if (!args[1]->IsUndefined()) {
    CHECK(args[1]->IsObject());
    CHECK(args[2]->IsUint32());
    CHECK(args[3]->IsBoolean());
    args.This()->SetInternalField(kAlgorithmField, args[1]);
    native->usages_mask_ = args[2].As<Uint32>()->Value();
    native->extractable_ = args[3]->IsTrue();
  }
}

void NativeCryptoKey::CreateCryptoKeyClass(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Isolate* isolate = env->isolate();

  CHECK_EQ(args.Length(), 1);
  Local<Value> callback = args[0];
  CHECK(callback->IsFunction());

  Local<FunctionTemplate> t =
      NewFunctionTemplate(isolate, NativeCryptoKey::New);
  t->InstanceTemplate()->SetInternalFieldCount(
      NativeCryptoKey::kInternalFieldCount);
  CHECK(env->crypto_cryptokey_constructor_template().IsEmpty());
  env->set_crypto_cryptokey_constructor_template(t);

  Local<Value> ctor;
  if (!t->GetFunction(env->context()).ToLocal(&ctor)) return;

  Local<Value> recv = Undefined(env->isolate());
  Local<Value> ret_v;
  if (!callback.As<Function>()
           ->Call(env->context(), recv, 1, &ctor)
           .ToLocal(&ret_v)) {
    return;
  }
  Local<Array> ret = ret_v.As<Array>();
  Local<Value> internal_ctor_v;
  if (!ret->Get(env->context(), 1).ToLocal(&internal_ctor_v)) return;
  CHECK(env->crypto_internal_cryptokey_constructor().IsEmpty());
  env->set_crypto_internal_cryptokey_constructor(
      internal_ctor_v.As<Function>());
  args.GetReturnValue().Set(ret);
}

// Returns all of the key's internal slot values as a single Array:
// [type enum, extractable, algorithm, usages mask, handle]. JS-side helpers
// call this once per key to prime a per-instance cache, so subsequent
// reads don't need to cross into C++ at all.
void NativeCryptoKey::GetSlots(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK_EQ(args.Length(), 1);
  if (!HasInstance(env, args[0])) {
    THROW_ERR_INVALID_THIS(env, "Value of \"this\" must be of type CryptoKey");
    return;
  }
  Local<Object> obj = args[0].As<Object>();
  NativeCryptoKey* native = Unwrap<NativeCryptoKey>(obj);
  CHECK_NOT_NULL(native);

  Local<Object> handle;
  if (!KeyObjectHandle::Create(env, native->handle_data_).ToLocal(&handle)) {
    return;
  }

  Local<Value> algorithm = obj->GetInternalField(kAlgorithmField).As<Value>();
  CHECK(algorithm->IsObject());
  Isolate* isolate = env->isolate();
  Local<Value> slots[] = {
      Uint32::NewFromUnsigned(isolate, native->handle_data_.GetKeyType()),
      v8::Boolean::New(isolate, native->extractable_),
      algorithm,
      Uint32::NewFromUnsigned(isolate, native->usages_mask_),
      handle,
  };
  args.GetReturnValue().Set(Array::New(isolate, slots, arraysize(slots)));
}

BaseObject::TransferMode NativeCryptoKey::GetTransferMode() const {
  return BaseObject::TransferMode::kCloneable;
}

std::unique_ptr<worker::TransferData> NativeCryptoKey::CloneForMessaging()
    const {
  Isolate* isolate = env()->isolate();
  Local<Object> obj = object();
  Local<Value> algorithm_v = obj->GetInternalField(kAlgorithmField).As<Value>();
  CHECK(algorithm_v->IsObject());
  v8::Global<Object> algorithm_copy(isolate, algorithm_v.As<Object>());
  return std::make_unique<CryptoKeyTransferData>(
      handle_data_, std::move(algorithm_copy), usages_mask_, extractable_);
}

Maybe<void> NativeCryptoKey::FinalizeTransferRead(
    Local<Context> context, v8::ValueDeserializer* deserializer) {
  Local<Value> bundle_v;
  if (!deserializer->ReadValue(context).ToLocal(&bundle_v)) {
    return Nothing<void>();
  }
  CHECK(bundle_v->IsObject());
  Local<Object> bundle = bundle_v.As<Object>();
  Isolate* isolate = env()->isolate();
  Local<Object> obj = object();

  // The partially-initialized object produced by
  // CryptoKeyTransferData::Deserialize should not have algorithm set yet.
  CHECK(obj->GetInternalField(kAlgorithmField).As<Value>()->IsUndefined());

  Local<Value> algorithm_v;
  if (!bundle->Get(context, FIXED_ONE_BYTE_STRING(isolate, "algorithm"))
           .ToLocal(&algorithm_v)) {
    return Nothing<void>();
  }
  CHECK(algorithm_v->IsObject());
  obj->SetInternalField(kAlgorithmField, algorithm_v);

  Local<Value> usages_v;
  if (!bundle->Get(context, FIXED_ONE_BYTE_STRING(isolate, "usages"))
           .ToLocal(&usages_v)) {
    return Nothing<void>();
  }
  CHECK(usages_v->IsUint32());
  usages_mask_ = usages_v.As<Uint32>()->Value();

  Local<Value> extractable_v;
  if (!bundle->Get(context, FIXED_ONE_BYTE_STRING(isolate, "extractable"))
           .ToLocal(&extractable_v)) {
    return Nothing<void>();
  }
  CHECK(extractable_v->IsBoolean());
  extractable_ = extractable_v->IsTrue();

  return v8::JustVoid();
}

Maybe<bool> NativeCryptoKey::CryptoKeyTransferData::FinalizeTransferWrite(
    Local<Context> context, v8::ValueSerializer* serializer) {
  Isolate* isolate = Isolate::GetCurrent();
  CHECK(!algorithm_.IsEmpty());
  Local<Object> bundle = Object::New(isolate);
  Local<Value> algorithm_v = PersistentToLocal::Strong(algorithm_);
  if (bundle
          ->Set(
              context, FIXED_ONE_BYTE_STRING(isolate, "algorithm"), algorithm_v)
          .IsNothing() ||
      bundle
          ->Set(context,
                FIXED_ONE_BYTE_STRING(isolate, "usages"),
                Uint32::NewFromUnsigned(isolate, usages_mask_))
          .IsNothing() ||
      bundle
          ->Set(context,
                FIXED_ONE_BYTE_STRING(isolate, "extractable"),
                v8::Boolean::New(isolate, extractable_))
          .IsNothing()) {
    return Nothing<bool>();
  }
  auto ret = serializer->WriteValue(context, bundle);
  algorithm_.Reset();
  return ret;
}

BaseObjectPtr<BaseObject> NativeCryptoKey::CryptoKeyTransferData::Deserialize(
    Environment* env,
    Local<Context> context,
    std::unique_ptr<worker::TransferData> self) {
  if (context != env->context()) {
    THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
    return {};
  }

  // Reconstruct the KeyObjectHandle for the transferred KeyObjectData.
  Local<Object> handle;
  if (!KeyObjectHandle::Create(env, data_).ToLocal(&handle)) return {};

  // Make sure internal/crypto/keys has been loaded so that the
  // CryptoKey constructor is registered with the Environment.
  Isolate* isolate = env->isolate();
  Local<Value> arg = FIXED_ONE_BYTE_STRING(isolate, "internal/crypto/keys");
  if (env->builtin_module_require()
          ->Call(context, Null(isolate), 1, &arg)
          .IsEmpty()) {
    return {};
  }

  // Construct a partially-initialized InternalCryptoKey; algorithm,
  // usages mask and extractable are filled in via FinalizeTransferRead.
  Local<Function> cryptokey_ctor = env->crypto_internal_cryptokey_constructor();
  CHECK(!cryptokey_ctor.IsEmpty());
  Local<Value> ctor_args[] = {
      handle,
      Undefined(isolate),
      Undefined(isolate),
      Undefined(isolate),
  };
  Local<Value> cryptokey;
  if (!cryptokey_ctor->NewInstance(context, 4, ctor_args).ToLocal(&cryptokey)) {
    return {};
  }

  return BaseObjectPtr<BaseObject>(
      Unwrap<NativeCryptoKey>(cryptokey.As<Object>()));
}

void NativeCryptoKey::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("handle_data", handle_data_);
}

void NativeCryptoKey::CryptoKeyTransferData::MemoryInfo(
    MemoryTracker* tracker) const {
  tracker->TrackField("data", data_);
  tracker->TrackField("algorithm", algorithm_);
}

namespace Keys {
void Initialize(Environment* env, Local<Object> target) {
  target->Set(env->context(),
              FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"),
              KeyObjectHandle::Initialize(env)).Check();

  constexpr int kKeyEncodingPKCS1 =
      static_cast<int>(EVPKeyPointer::PKEncodingType::PKCS1);
  constexpr int kKeyEncodingPKCS8 =
      static_cast<int>(EVPKeyPointer::PKEncodingType::PKCS8);
  constexpr int kKeyEncodingSPKI =
      static_cast<int>(EVPKeyPointer::PKEncodingType::SPKI);
  constexpr int kKeyEncodingSEC1 =
      static_cast<int>(EVPKeyPointer::PKEncodingType::SEC1);
  constexpr int kKeyFormatDER =
      static_cast<int>(EVPKeyPointer::PKFormatType::DER);
  constexpr int kKeyFormatPEM =
      static_cast<int>(EVPKeyPointer::PKFormatType::PEM);
  constexpr int kKeyFormatJWK =
      static_cast<int>(EVPKeyPointer::PKFormatType::JWK);
  constexpr int kKeyFormatRawPublic =
      static_cast<int>(EVPKeyPointer::PKFormatType::RAW_PUBLIC);
  constexpr int kKeyFormatRawPrivate =
      static_cast<int>(EVPKeyPointer::PKFormatType::RAW_PRIVATE);
  constexpr int kKeyFormatRawSeed =
      static_cast<int>(EVPKeyPointer::PKFormatType::RAW_SEED);

  constexpr auto kSigEncDER = DSASigEnc::DER;
  constexpr auto kSigEncP1363 = DSASigEnc::P1363;

  NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatRaw);
  NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatPKCS8);
  NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatSPKI);
  NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
#if OPENSSL_WITH_PQC
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_44);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_65);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_87);
#if OPENSSL_WITH_PQC_ML_KEM_512
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_512);
#endif
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_768);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_1024);
#if OPENSSL_WITH_PQC_SLH_DSA
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_128F);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_128S);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_192F);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_192S);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_256F);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_256S);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_128F);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_128S);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_192F);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_192S);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_256F);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_256S);
#endif
#endif
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519);
  NODE_DEFINE_CONSTANT(target, EVP_PKEY_X448);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyFormatJWK);
  NODE_DEFINE_CONSTANT(target, kKeyFormatRawPublic);
  NODE_DEFINE_CONSTANT(target, kKeyFormatRawPrivate);
  NODE_DEFINE_CONSTANT(target, kKeyFormatRawSeed);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  NODE_DEFINE_CONSTANT(target, kSigEncDER);
  NODE_DEFINE_CONSTANT(target, kSigEncP1363);
}

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
  KeyObjectHandle::RegisterExternalReferences(registry);
}
}  // namespace Keys

}  // namespace crypto
}  // namespace node
