// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Boolberry developers
// Copyright (c) 2017-2025 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// You may obtain a copy of the licence at:
//
//     https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
//
// The EUPL is a copyleft licence that is compatible with the MIT/X11
// licence used by the original projects; the MIT terms are therefore
// considered “grandfathered” under the EUPL for this code.
//
// SPDX‑License‑Identifier: EUPL-1.2
//
#include "eth_signature.h"
#include "crypto.h"
#ifndef USE_OPEN_SSL_FOR_ECDSA
  #include "bitcoin-secp256k1/include/secp256k1.h"
#endif
#include "random.h"
#include "misc_language.h"
#include <string_tools.h>


#ifdef USE_OPEN_SSL_FOR_ECDSA
  #include <openssl/ec.h>
  #include <openssl/ecdsa.h>
  #include <openssl/obj_mac.h>
  #include <openssl/bn.h>
  #include <openssl/rand.h>
#endif


// Function to create EC_KEY from raw 32 - byte private key
EC_KEY * create_ec_key_from_private_key(const unsigned char* private_key) {
  EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1);
  if (!key) {
    std::cerr << "Failed to create new EC Key" << std::endl;
    return nullptr;
  }

  BIGNUM* priv_key_bn = BN_bin2bn(private_key, 32, nullptr);
  if (!priv_key_bn) {
    std::cerr << "Failed to convert private key to BIGNUM" << std::endl;
    EC_KEY_free(key);
    return nullptr;
  }

  if (!EC_KEY_set_private_key(key, priv_key_bn)) {
    std::cerr << "Failed to set private key" << std::endl;
    EC_KEY_free(key);
    BN_free(priv_key_bn);
    return nullptr;
  }

  BN_free(priv_key_bn);
  return key;
}


void ensure_canonical_s(BIGNUM* s, const EC_GROUP* group) {
  // Get the order of the curve
  BIGNUM* order = BN_new();
  EC_GROUP_get_order(group, order, nullptr);

  // Compute half of the order: `n / 2`
  BIGNUM* half_order = BN_new();
  BN_rshift1(half_order, order);

  // If `s` is greater than `n / 2`, replace `s` with `n - s`
  if (BN_cmp(s, half_order) > 0) {
    BN_sub(s, order, s);
  }

  BN_free(order);
  BN_free(half_order);
}

// Update the function to ensure canonical `s`
bool generate_ethereum_signature(const unsigned char* hash, const unsigned char* private_key, crypto::eth_signature& sig_res) {
  EC_KEY* ec_key = create_ec_key_from_private_key(private_key);
  if (!ec_key) {
    throw std::runtime_error("Failed to create EC key from private key");
  }

  // Sign the hash
  unsigned int sig_len = ECDSA_size(ec_key);
  std::vector<unsigned char> signature(sig_len);
  if (ECDSA_sign(0, hash, 32, signature.data(), &sig_len, ec_key) == 0) {
    EC_KEY_free(ec_key);
    throw std::runtime_error("Failed to create signature");
  }
  signature.resize(sig_len);

 
  // The OpenSSL ECDSA signature output is DER encoded, Ethereum expects (r, s, v)
  const unsigned char* p = signature.data();
  ECDSA_SIG* sig = d2i_ECDSA_SIG(nullptr, &p, sig_len);
  if (!sig) {
    EC_KEY_free(ec_key);
    throw std::runtime_error("Failed to parse ECDSA signature");
  }

  const BIGNUM* r = nullptr;
  const BIGNUM* s = nullptr;
  ECDSA_SIG_get0(sig, &r, &s);

  // Ensure canonical `s`
  BIGNUM* s_canonical = BN_dup(s);
  ensure_canonical_s(s_canonical, EC_KEY_get0_group(ec_key));

  BN_bn2binpad(r, (unsigned char* )&sig_res.data[0], 32);
  BN_bn2binpad(s_canonical, (unsigned char*)&sig_res.data[32], 32);


  ECDSA_SIG_free(sig);
  BN_free(s_canonical);
  EC_KEY_free(ec_key);
  return true;
}

// Convert raw 33-byte compressed public key to EC_KEY object
EC_KEY* create_ec_key_from_compressed_public_key(const unsigned char* compressed_pub_key) {
  EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1);
  if (!key) {
    std::cerr << "Failed to create EC_KEY object" << std::endl;
    return nullptr;
  }

  EC_POINT* pub_point = EC_POINT_new(EC_KEY_get0_group(key));
  if (!EC_POINT_oct2point(EC_KEY_get0_group(key), pub_point, compressed_pub_key, 33, nullptr)) {
    std::cerr << "Failed to convert compressed public key" << std::endl;
    EC_POINT_free(pub_point);
    EC_KEY_free(key);
    return nullptr;
  }

  if (!EC_KEY_set_public_key(key, pub_point)) {
    std::cerr << "Failed to set public key" << std::endl;
    EC_POINT_free(pub_point);
    EC_KEY_free(key);
    return nullptr;
  }

  EC_POINT_free(pub_point);
  return key;
}

// Function to verify Ethereum-compatible signature
bool verify_ethereum_signature(const crypto::hash& m, const crypto::eth_signature& sig_res, const crypto::eth_public_key& compressed_pub_key) {
  EC_KEY* ec_key = create_ec_key_from_compressed_public_key((const unsigned char*)&compressed_pub_key.data[0]);
  if (!ec_key) {
    throw std::runtime_error("Failed to create EC key from compressed public key");
  }
  const unsigned char* r = (unsigned char*)&sig_res.data[0];
  const unsigned char* s = (unsigned char*)&sig_res.data[32];
  const unsigned char* hash = (unsigned char*)&m;

  // Create ECDSA_SIG from r and s
  BIGNUM* bn_r = BN_bin2bn(r, 32, nullptr);
  BIGNUM* bn_s = BN_bin2bn(s, 32, nullptr);
  if (!bn_r || !bn_s) {
    EC_KEY_free(ec_key);
    BN_free(bn_r);
    BN_free(bn_s);
    throw std::runtime_error("Failed to convert r or s to BIGNUM");
  }

  ECDSA_SIG* sig = ECDSA_SIG_new();
  if (!sig) {
    EC_KEY_free(ec_key);
    BN_free(bn_r);
    BN_free(bn_s);
    throw std::runtime_error("Failed to create ECDSA_SIG object");
  }

  if (!ECDSA_SIG_set0(sig, bn_r, bn_s)) {
    EC_KEY_free(ec_key);
    ECDSA_SIG_free(sig);
    BN_free(bn_r);
    BN_free(bn_s);
    throw std::runtime_error("Failed to set r and s in ECDSA_SIG");
  }

  // Verify the signature
  int verification_result = ECDSA_do_verify(hash, 32, sig, ec_key);

  ECDSA_SIG_free(sig);
  EC_KEY_free(ec_key);


  return verification_result == 1;
}


// 
// struct KeyPair {
//   std::vector<unsigned char> private_key;  // 32 bytes
//   std::vector<unsigned char> public_key;   // 33 bytes (compressed format)
// };

// Function to generate an Ethereum-compatible key pair
bool generate_ethereum_key_pair(crypto::eth_secret_key& sec_key, crypto::eth_public_key& pub_key) {
  /*KeyPair keypair;*/

  // Create a new EC_KEY object with the secp256k1 curve
  EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1);
  if (!key) {
    throw std::runtime_error("Failed to create new EC_KEY object");
  }

  // Generate the key pair
  if (EC_KEY_generate_key(key) == 0) {
    EC_KEY_free(key);
    throw std::runtime_error("Failed to generate key pair");
  }

  // Extract the private key
  const BIGNUM* priv_bn = EC_KEY_get0_private_key(key);
  if (!priv_bn) {
    EC_KEY_free(key);
    throw std::runtime_error("Failed to get private key");
  }

  BN_bn2binpad(priv_bn, (unsigned char*)&sec_key.data[0], 32);

  // Extract the public key in compressed format
  const EC_POINT* pub_point = EC_KEY_get0_public_key(key);
  if (!pub_point) {
    EC_KEY_free(key);
    throw std::runtime_error("Failed to get public key");
  }

  //keypair.public_key.resize(33);  // Compressed format
  if (EC_POINT_point2oct(EC_KEY_get0_group(key), pub_point, POINT_CONVERSION_COMPRESSED,
    (unsigned char*)&pub_key.data[0], sizeof(pub_key.data), nullptr) == 0) {
    EC_KEY_free(key);
    throw std::runtime_error("Failed to convert public key to compressed format");
  }

  EC_KEY_free(key);
  return true;
}





namespace crypto
{
  bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept
  {
    try
    {
#ifndef USE_OPEN_SSL_FOR_ECDSA
      secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
      auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){
          secp256k1_context_destroy(ctx);
          ctx = nullptr;
        });

      uint8_t randomness[32];
      crypto::generate_random_bytes(sizeof randomness, randomness);
      if (!secp256k1_context_randomize(ctx, randomness))
        return false;

      for(size_t i = 1024; i != 0; --i)
      {
        crypto::generate_random_bytes(sizeof sec_key, sec_key.data);
        if (secp256k1_ec_seckey_verify(ctx, sec_key.data))
          break;
        if (i == 1)
          return false;
      }

      secp256k1_pubkey uncompressed_pub_key{};
      if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data))
        return false;

      size_t output_len = sizeof pub_key;
      if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED))
        return false;

      return true;
#else
      return generate_ethereum_key_pair(sec_key, pub_key);
#endif
    }
    catch(...)
    {
      return false;
    }
  }

#ifndef USE_OPEN_SSL_FOR_ECDSA
  bool eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept
  {
    try
    {
      // TODO: do we need this? consider using static context
      secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
      auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){
        secp256k1_context_destroy(ctx);
        ctx = nullptr;
        });

      secp256k1_pubkey uncompressed_pub_key{};
      if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data))
        return false;

      size_t output_len = sizeof pub_key;
      if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED))
        return false;

      return true;
    }
    catch(...)
    {
      return false;
    }
  }
#endif
  // generates secp256k1 ECDSA signature
  bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept
  {
    try
    {
#ifndef USE_OPEN_SSL_FOR_ECDSA
      secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
      auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){
          secp256k1_context_destroy(ctx);
          ctx = nullptr;
        });

      uint8_t randomness[32];
      crypto::generate_random_bytes(sizeof randomness, randomness);
      if (!secp256k1_context_randomize(ctx, randomness))
        return false;
      
      secp256k1_ecdsa_signature secp256k1_ecdsa_sig{};
      if (!secp256k1_ecdsa_sign(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, sec_key.data, NULL, NULL))
        return false;

      if (!secp256k1_ecdsa_signature_serialize_compact(ctx, sig.data, &secp256k1_ecdsa_sig))
        return false;
      
      return true;
#else 
      return generate_ethereum_signature((const unsigned char*)&m.data, (unsigned char*)&sec_key.data, sig);
#endif
    }
    catch(...)
    {
      return false;
    }
  }

  // verifies secp256k1 ECDSA signature
  bool verify_eth_signature(const hash& m, const eth_public_key& pub_key, const eth_signature& sig) noexcept
  {
    try
    {
      // TODO (performance) consider using secp256k1_context_static for verification -- sowle
#ifndef USE_OPEN_SSL_FOR_ECDSA
      secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
      auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){
          secp256k1_context_destroy(ctx);
          ctx = nullptr;
        });

      uint8_t randomness[32];
      crypto::generate_random_bytes(sizeof randomness, randomness);
      if (!secp256k1_context_randomize(ctx, randomness))
        return false;

      secp256k1_ecdsa_signature secp256k1_ecdsa_sig{};
      secp256k1_pubkey uncompressed_pub_key{};

      if (!secp256k1_ecdsa_signature_parse_compact(ctx, &secp256k1_ecdsa_sig, sig.data))
        return false;

      if (!secp256k1_ec_pubkey_parse(ctx, &uncompressed_pub_key, pub_key.data, sizeof pub_key))
        return false;

      // verify a signature
      if (!secp256k1_ecdsa_verify(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, &uncompressed_pub_key))
        return false;

      return true;
#else
      return verify_ethereum_signature(m, sig, pub_key);
#endif
    }
    catch(...)
    {
      return false;
    }
  }

  std::ostream& operator<<(std::ostream& o, const eth_secret_key& v)
  {
    return o << epee::string_tools::pod_to_hex(v);
  }

  std::ostream& operator<<(std::ostream& o, const eth_public_key& v)
  {
    return o << epee::string_tools::pod_to_hex(v);
  }
  std::ostream& operator<<(std::ostream& o, const eth_signature& v)
  {
    return o << epee::string_tools::pod_to_hex(v);
  }


} // namespace crypto
