#include "crypto/crypto_ec.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "v8.h"

#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/ecdh.h>

#include <algorithm>

namespace node {

using ncrypto::BignumPointer;
using ncrypto::DataPointer;
using ncrypto::Ec;
using ncrypto::ECGroupPointer;
using ncrypto::ECKeyPointer;
using ncrypto::ECPointPointer;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::MarkPopErrorOnReturn;
using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::LocalVector;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;

namespace crypto {

void ECDH::Initialize(Environment* env, Local<Object> target) {
  Isolate* isolate = env->isolate();
  Local<Context> context = env->context();

  Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);

  t->InstanceTemplate()->SetInternalFieldCount(ECDH::kInternalFieldCount);

  SetProtoMethod(isolate, t, "generateKeys", GenerateKeys);
  SetProtoMethod(isolate, t, "computeSecret", ComputeSecret);
  SetProtoMethodNoSideEffect(isolate, t, "getPublicKey", GetPublicKey);
  SetProtoMethodNoSideEffect(isolate, t, "getPrivateKey", GetPrivateKey);
  SetProtoMethod(isolate, t, "setPublicKey", SetPublicKey);
  SetProtoMethod(isolate, t, "setPrivateKey", SetPrivateKey);

  SetConstructorFunction(context, target, "ECDH", t);

  SetMethodNoSideEffect(context, target, "ECDHConvertKey", ECDH::ConvertKey);
  SetMethodNoSideEffect(context, target, "getCurves", ECDH::GetCurves);

  ECKeyPairGenJob::Initialize(env, target);

  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
}

void ECDH::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
  registry->Register(New);
  registry->Register(GenerateKeys);
  registry->Register(ComputeSecret);
  registry->Register(GetPublicKey);
  registry->Register(GetPrivateKey);
  registry->Register(SetPublicKey);
  registry->Register(SetPrivateKey);
  registry->Register(ECDH::ConvertKey);
  registry->Register(ECDH::GetCurves);

  ECKeyPairGenJob::RegisterExternalReferences(registry);
}

void ECDH::GetCurves(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  LocalVector<Value> arr(env->isolate());
  Ec::GetCurves([&](std::string_view curve) -> bool {
    arr.push_back(OneByteString(env->isolate(), curve));
    return true;
  });
  args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size()));
}

ECDH::ECDH(Environment* env, Local<Object> wrap, ECKeyPointer&& key)
    : BaseObject(env, wrap), key_(std::move(key)), group_(key_.getGroup()) {
  MakeWeak();
  CHECK_NOT_NULL(group_);
}

void ECDH::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackFieldWithSize("key", key_ ? kSizeOf_EC_KEY : 0);
}

void ECDH::New(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  MarkPopErrorOnReturn mark_pop_error_on_return;

  // TODO(indutny): Support raw curves?
  CHECK(args[0]->IsString());
  node::Utf8Value curve(env->isolate(), args[0]);

  int nid = OBJ_sn2nid(*curve);
  if (nid == NID_undef)
    return THROW_ERR_CRYPTO_INVALID_CURVE(env);

  auto key = ECKeyPointer::NewByCurveName(nid);
  if (!key)
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
      "Failed to create key using named curve");

  new ECDH(env, args.This(), std::move(key));
}

void ECDH::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  ECDH* ecdh;
  ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());

  if (!ecdh->key_.generate()) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to generate key");
  }
}

ECPointPointer ECDH::BufferToPoint(Environment* env,
                                   const EC_GROUP* group,
                                   Local<Value> buf) {
  ArrayBufferOrViewContents<unsigned char> input(buf);
  if (!input.CheckSizeInt32()) [[unlikely]] {
    THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
    return {};
  }

  auto pub = ECPointPointer::New(group);
  if (!pub) {
    THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to allocate EC_POINT for a public key");
    return pub;
  }

  ncrypto::Buffer<const unsigned char> buffer{
      .data = input.data(),
      .len = input.size(),
  };
  if (!pub.setFromBuffer(buffer, group)) {
    return {};
  }

  return pub;
}

void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK(IsAnyBufferSource(args[0]));

  ECDH* ecdh;
  ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());

  MarkPopErrorOnReturn mark_pop_error_on_return;

  if (!ecdh->IsKeyPairValid())
    return THROW_ERR_CRYPTO_INVALID_KEYPAIR(env);

  auto pub = ECDH::BufferToPoint(env, ecdh->group_, args[0]);
  if (!pub) {
    args.GetReturnValue().Set(
        FIXED_ONE_BYTE_STRING(env->isolate(),
        "ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY"));
    return;
  }

  int field_size = EC_GROUP_get_degree(ecdh->group_);
  size_t out_len = (field_size + 7) / 8;
  auto bs = ArrayBuffer::NewBackingStore(
      env->isolate(), out_len, BackingStoreInitializationMode::kUninitialized);

  if (!ECDH_compute_key(
          bs->Data(), bs->ByteLength(), pub, ecdh->key_.get(), nullptr))
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to compute ECDH key");

  Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
  Local<Value> buffer;
  if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return;
  args.GetReturnValue().Set(buffer);
}

void ECDH::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  // Conversion form
  CHECK_EQ(args.Length(), 1);

  ECDH* ecdh;
  ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());

  const auto group = ecdh->key_.getGroup();
  const auto pub = ecdh->key_.getPublicKey();
  if (pub == nullptr)
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to get ECDH public key");

  CHECK(args[0]->IsUint32());
  uint32_t val = args[0].As<Uint32>()->Value();
  point_conversion_form_t form = static_cast<point_conversion_form_t>(val);

  Local<Object> buf;
  if (ECPointToBuffer(env, group, pub, form).ToLocal(&buf)) {
    args.GetReturnValue().Set(buf);
  }
}

void ECDH::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  ECDH* ecdh;
  ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());

  auto b = ecdh->key_.getPrivateKey();
  if (b == nullptr)
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to get ECDH private key");

  auto bs = ArrayBuffer::NewBackingStore(
      env->isolate(),
      BignumPointer::GetByteCount(b),
      BackingStoreInitializationMode::kUninitialized);

  CHECK_EQ(bs->ByteLength(),
           BignumPointer::EncodePaddedInto(
               b, static_cast<unsigned char*>(bs->Data()), bs->ByteLength()));

  Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
  Local<Value> buffer;
  if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return;
  args.GetReturnValue().Set(buffer);
}

void ECDH::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  ECDH* ecdh;
  ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());

  ArrayBufferOrViewContents<unsigned char> priv_buffer(args[0]);
  if (!priv_buffer.CheckSizeInt32()) [[unlikely]]
    return THROW_ERR_OUT_OF_RANGE(env, "key is too big");

  BignumPointer priv(priv_buffer.data(), priv_buffer.size());
  if (!priv) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to convert Buffer to BN");
  }

  if (!ecdh->IsKeyValidForCurve(priv)) {
    return THROW_ERR_CRYPTO_INVALID_KEYTYPE(env,
        "Private key is not valid for specified curve.");
  }

  auto new_key = ecdh->key_.clone();
  CHECK(new_key);

  bool result = new_key.setPrivateKey(priv);
  priv.reset();

  if (!result) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to convert BN to a private key");
  }

  MarkPopErrorOnReturn mark_pop_error_on_return;
  USE(&mark_pop_error_on_return);

  auto priv_key = new_key.getPrivateKey();
  CHECK_NOT_NULL(priv_key);

  auto pub = ECPointPointer::New(ecdh->group_);
  CHECK(pub);

  if (!pub.mul(ecdh->group_, priv_key)) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to generate ECDH public key");
  }

  if (!new_key.setPublicKey(pub)) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to set generated public key");
  }

  ecdh->key_ = std::move(new_key);
  ecdh->group_ = ecdh->key_.getGroup();
}

void ECDH::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  ECDH* ecdh;
  ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());

  CHECK(IsAnyBufferSource(args[0]));

  MarkPopErrorOnReturn mark_pop_error_on_return;

  auto pub = ECDH::BufferToPoint(env, ecdh->group_, args[0]);
  if (!pub) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to convert Buffer to EC_POINT");
  }

  if (!ecdh->key_.setPublicKey(pub)) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to set EC_POINT as the public key");
  }
}

bool ECDH::IsKeyValidForCurve(const BignumPointer& private_key) {
  CHECK(group_);
  CHECK(private_key);
  // Private keys must be in the range [1, n-1].
  // Ref: Section 3.2.1 - http://www.secg.org/sec1-v2.pdf
  if (private_key < BignumPointer::One()) {
    return false;
  }
  auto order = BignumPointer::New();
  CHECK(order);
  return EC_GROUP_get_order(group_, order.get(), nullptr) &&
         private_key < order;
}

bool ECDH::IsKeyPairValid() {
  MarkPopErrorOnReturn mark_pop_error_on_return;
  return key_.checkKey();
}

// Convert the input public key to compressed, uncompressed, or hybrid formats.
void ECDH::ConvertKey(const FunctionCallbackInfo<Value>& args) {
  MarkPopErrorOnReturn mark_pop_error_on_return;
  Environment* env = Environment::GetCurrent(args);

  CHECK_EQ(args.Length(), 3);
  CHECK(IsAnyBufferSource(args[0]));

  ArrayBufferOrViewContents<char> args0(args[0]);
  if (!args0.CheckSizeInt32()) [[unlikely]]
    return THROW_ERR_OUT_OF_RANGE(env, "key is too big");
  if (args0.empty()) return args.GetReturnValue().SetEmptyString();

  node::Utf8Value curve(env->isolate(), args[1]);

  int nid = OBJ_sn2nid(*curve);
  if (nid == NID_undef)
    return THROW_ERR_CRYPTO_INVALID_CURVE(env);

  auto group = ECGroupPointer::NewByCurveName(nid);
  if (!group)
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get EC_GROUP");

  auto pub = ECDH::BufferToPoint(env, group, args[0]);
  if (!pub) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
        "Failed to convert Buffer to EC_POINT");
  }

  CHECK(args[2]->IsUint32());
  uint32_t val = args[2].As<Uint32>()->Value();
  point_conversion_form_t form = static_cast<point_conversion_form_t>(val);

  Local<Object> buf;
  if (ECPointToBuffer(env, group, pub, form).ToLocal(&buf)) {
    args.GetReturnValue().Set(buf);
  }
}

EVPKeyCtxPointer EcKeyGenTraits::Setup(EcKeyPairGenConfig* params) {
  EVPKeyCtxPointer key_ctx;
  switch (params->params.curve_nid) {
    case EVP_PKEY_ED25519:
      // Fall through
    case EVP_PKEY_ED448:
      // Fall through
    case EVP_PKEY_X25519:
      // Fall through
    case EVP_PKEY_X448:
      key_ctx = EVPKeyCtxPointer::NewFromID(params->params.curve_nid);
      break;
    default: {
      auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_EC);
      if (!param_ctx.initForParamgen() ||
          !param_ctx.setEcParameters(params->params.curve_nid,
                                     params->params.param_encoding)) {
        return {};
      }

      auto key_params = param_ctx.paramgen();
      if (!key_params) return {};

      key_ctx = key_params.newCtx();
    }
  }

  if (!key_ctx.initForKeygen()) return {};
  return key_ctx;
}

// EcKeyPairGenJob input arguments
//   1. CryptoJobMode
//   2. Curve Name
//   3. Param Encoding
//   4. Public Format
//   5. Public Type
//   6. Private Format
//   7. Private Type
//   8. Cipher
//   9. Passphrase
Maybe<void> EcKeyGenTraits::AdditionalConfig(
    CryptoJobMode mode,
    const FunctionCallbackInfo<Value>& args,
    unsigned int* offset,
    EcKeyPairGenConfig* params) {
  Environment* env = Environment::GetCurrent(args);
  CHECK(args[*offset]->IsString());  // curve name

  Utf8Value curve_name(env->isolate(), args[*offset]);
  params->params.curve_nid = Ec::GetCurveIdFromName(*curve_name);
  if (params->params.curve_nid == NID_undef) {
    THROW_ERR_CRYPTO_INVALID_CURVE(env);
    return Nothing<void>();
  }

  // param encoding
  if (args[*offset + 1]->IsNullOrUndefined()) {
    params->params.param_encoding = OPENSSL_EC_NAMED_CURVE;
  } else {
    CHECK(args[*offset + 1]->IsInt32());
    params->params.param_encoding = args[*offset + 1].As<Int32>()->Value();
    if (params->params.param_encoding != OPENSSL_EC_NAMED_CURVE &&
        params->params.param_encoding != OPENSSL_EC_EXPLICIT_CURVE) {
      THROW_ERR_OUT_OF_RANGE(env, "Invalid param_encoding specified");
      return Nothing<void>();
    }
  }

  *offset += 2;

  return JustVoid();
}

bool ExportJWKEcKey(Environment* env,
                    const KeyObjectData& key,
                    Local<Object> target) {
  Mutex::ScopedLock lock(key.mutex());
  const auto& m_pkey = key.GetAsymmetricKey();
  CHECK_EQ(m_pkey.id(), EVP_PKEY_EC);

  const EC_KEY* ec = m_pkey;
  CHECK_NOT_NULL(ec);

  const auto pub = ECKeyPointer::GetPublicKey(ec);
  const auto group = ECKeyPointer::GetGroup(ec);

  int degree_bits = EC_GROUP_get_degree(group);
  int degree_bytes =
    (degree_bits / CHAR_BIT) + (7 + (degree_bits % CHAR_BIT)) / 8;

  auto x = BignumPointer::New();
  auto y = BignumPointer::New();

  if (!EC_POINT_get_affine_coordinates(group, pub, x.get(), y.get(), nullptr)) {
    ThrowCryptoError(env, ERR_get_error(),
                     "Failed to get elliptic-curve point coordinates");
    return false;
  }

  if (!target
           ->DefineOwnProperty(
               env->context(), env->jwk_kty_string(), env->jwk_ec_string())
           .FromMaybe(false)) {
    return false;
  }

  if (SetEncodedValue(
          env,
          target,
          env->jwk_x_string(),
          x.get(),
          degree_bytes).IsNothing() ||
      SetEncodedValue(
          env,
          target,
          env->jwk_y_string(),
          y.get(),
          degree_bytes).IsNothing()) {
    return false;
  }

  Local<String> crv_name;
  const int nid = EC_GROUP_get_curve_name(group);
  switch (nid) {
    case NID_X9_62_prime256v1:
      crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "P-256");
      break;
    case NID_secp256k1:
      crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "secp256k1");
      break;
    case NID_secp384r1:
      crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "P-384");
      break;
    case NID_secp521r1:
      crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "P-521");
      break;
    default: {
      THROW_ERR_CRYPTO_JWK_UNSUPPORTED_CURVE(
          env, "Unsupported JWK EC curve: %s.", OBJ_nid2sn(nid));
      return false;
    }
  }
  if (!target
           ->DefineOwnProperty(env->context(), env->jwk_crv_string(), crv_name)
           .FromMaybe(false)) {
    return false;
  }

  if (key.GetKeyType() == kKeyTypePrivate) {
    auto pvt = ECKeyPointer::GetPrivateKey(ec);
    return SetEncodedValue(env, target, env->jwk_d_string(), pvt, degree_bytes)
        .IsJust();
  }

  return true;
}

bool ExportJWKEdKey(Environment* env,
                    const KeyObjectData& key,
                    Local<Object> target) {
  Mutex::ScopedLock lock(key.mutex());
  const auto& pkey = key.GetAsymmetricKey();

  const char* curve = ([&] {
    switch (pkey.id()) {
      case EVP_PKEY_ED25519:
        return "Ed25519";
      case EVP_PKEY_ED448:
        return "Ed448";
      case EVP_PKEY_X25519:
        return "X25519";
      case EVP_PKEY_X448:
        return "X448";
      default:
        UNREACHABLE();
    }
  })();

  static constexpr auto trySetKey = [](Environment* env,
                                       DataPointer data,
                                       Local<Object> target,
                                       Local<String> key) {
    Local<Value> encoded;
    if (!data) return false;
    const ncrypto::Buffer<const char> out = data;
    return StringBytes::Encode(env->isolate(), out.data, out.len, BASE64URL)
               .ToLocal(&encoded) &&
           target->DefineOwnProperty(env->context(), key, encoded)
               .FromMaybe(false);
  };

  return !(
      !target
           ->DefineOwnProperty(env->context(),
                               env->jwk_crv_string(),
                               OneByteString(env->isolate(), curve))
           .FromMaybe(false) ||
      (key.GetKeyType() == kKeyTypePrivate &&
       !trySetKey(env, pkey.rawPrivateKey(), target, env->jwk_d_string())) ||
      !trySetKey(env, pkey.rawPublicKey(), target, env->jwk_x_string()) ||
      !target
           ->DefineOwnProperty(
               env->context(), env->jwk_kty_string(), env->jwk_okp_string())
           .FromMaybe(false));
}
KeyObjectData ImportJWKEdKey(Environment* env, Local<Object> jwk) {
  Local<Value> crv_value;
  Local<Value> x_value;
  Local<Value> d_value;

  if (!jwk->Get(env->context(), env->jwk_crv_string()).ToLocal(&crv_value) ||
      !jwk->Get(env->context(), env->jwk_x_string()).ToLocal(&x_value) ||
      !jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value)) {
    return {};
  }

  if (!crv_value->IsString() || !x_value->IsString() ||
      (!d_value->IsUndefined() && !d_value->IsString())) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key");
    return {};
  }

  Utf8Value crv(env->isolate(), crv_value.As<String>());

  static constexpr struct {
    const char* name;
    int nid;
  } kCurveToNid[] = {
      {"Ed25519", EVP_PKEY_ED25519},
      {"Ed448", EVP_PKEY_ED448},
      {"X25519", EVP_PKEY_X25519},
      {"X448", EVP_PKEY_X448},
  };

  int id = NID_undef;
  for (const auto& entry : kCurveToNid) {
    if (strcmp(*crv, entry.name) == 0) {
      id = entry.nid;
      break;
    }
  }

  if (id == NID_undef) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key");
    return {};
  }

  KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic;

  ByteSource raw;
  if (type == kKeyTypePrivate) {
    raw = ByteSource::FromEncodedString(env, d_value.As<String>());
  } else {
    raw = ByteSource::FromEncodedString(env, x_value.As<String>());
  }

  typedef EVPKeyPointer (*new_key_fn)(
      int, const ncrypto::Buffer<const unsigned char>&);
  new_key_fn fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate
                                          : EVPKeyPointer::NewRawPublic;

  auto pkey = fn(id,
                 ncrypto::Buffer<const unsigned char>{
                     .data = raw.data<const unsigned char>(),
                     .len = raw.size(),
                 });
  if (!pkey) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key");
    return {};
  }

  // When importing a private key, verify that the JWK's x field matches
  // the public key derived from the private key.
  if (type == kKeyTypePrivate && x_value->IsString()) {
    ByteSource x = ByteSource::FromEncodedString(env, x_value.As<String>());
    auto derived_pub = pkey.rawPublicKey();
    if (!derived_pub || derived_pub.size() != x.size() ||
        CRYPTO_memcmp(derived_pub.get(), x.data(), x.size()) != 0) {
      THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key");
      return {};
    }
  }

  return KeyObjectData::CreateAsymmetric(type, std::move(pkey));
}
KeyObjectData ImportJWKEcKey(Environment* env, Local<Object> jwk) {
  Local<Value> crv_value;
  if (!jwk->Get(env->context(), env->jwk_crv_string()).ToLocal(&crv_value) ||
      !crv_value->IsString()) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
    return {};
  }

  Utf8Value curve(env->isolate(), crv_value.As<String>());
  int nid = Ec::GetCurveIdFromName(*curve);
  if (nid == NID_undef) {  // Unknown curve
    THROW_ERR_CRYPTO_INVALID_CURVE(env);
    return {};
  }

  Local<Value> x_value;
  Local<Value> y_value;
  Local<Value> d_value;

  if (!jwk->Get(env->context(), env->jwk_x_string()).ToLocal(&x_value) ||
      !jwk->Get(env->context(), env->jwk_y_string()).ToLocal(&y_value) ||
      !jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value)) {
    return {};
  }

  if (!x_value->IsString() ||
      !y_value->IsString() ||
      (!d_value->IsUndefined() && !d_value->IsString())) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
    return {};
  }

  KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic;

  auto ec = ECKeyPointer::NewByCurveName(nid);
  if (!ec) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
    return {};
  }

  ByteSource x = ByteSource::FromEncodedString(env, x_value.As<String>());
  ByteSource y = ByteSource::FromEncodedString(env, y_value.As<String>());

  // setPublicKeyRaw validates the point is on the curve. For h=1 curves
  // (P-256/P-384/P-521), this skips EC_KEY_check_key for efficiency.
  if (!ec.setPublicKeyRaw(x.ToBN(), y.ToBN())) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
    return {};
  }

  if (type == kKeyTypePrivate) {
    ByteSource d = ByteSource::FromEncodedString(env, d_value.As<String>());
    if (!ec.setPrivateKey(d.ToBN())) {
      THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
      return {};
    }
    // Verify that the public point matches the private scalar (d*G == (x,y)).
    if (!ec.checkKey()) {
      THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
      return {};
    }
  }

  auto pkey = EVPKeyPointer::New();
  if (!pkey) return {};
  CHECK(pkey.set(ec));

  return KeyObjectData::CreateAsymmetric(type, std::move(pkey));
}

bool GetEcKeyDetail(Environment* env,
                    const KeyObjectData& key,
                    Local<Object> target) {
  Mutex::ScopedLock lock(key.mutex());
  const auto& m_pkey = key.GetAsymmetricKey();
  CHECK_EQ(m_pkey.id(), EVP_PKEY_EC);

  const EC_KEY* ec = m_pkey;
  CHECK_NOT_NULL(ec);

  const auto group = ECKeyPointer::GetGroup(ec);
  int nid = EC_GROUP_get_curve_name(group);

  return target
      ->Set(env->context(),
            env->named_curve_string(),
            OneByteString(env->isolate(), OBJ_nid2sn(nid)))
      .IsJust();
}

// WebCrypto requires a different format for ECDSA signatures than
// what OpenSSL produces, so we need to convert between them. The
// implementation here is a adapted from Chromium's impl here:
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/ecdsa.cc

size_t GroupOrderSize(const EVPKeyPointer& key) {
  const EC_KEY* ec = key;
  CHECK_NOT_NULL(ec);
  auto order = BignumPointer::New();
  CHECK(order);
  CHECK(EC_GROUP_get_order(ECKeyPointer::GetGroup(ec), order.get(), nullptr));
  return order.byteLength();
}
}  // namespace crypto
}  // namespace node
