/*
* (C) 2024 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/ec_scalar.h>

#include <botan/ec_group.h>
#include <botan/internal/ec_inner_data.h>
#include <algorithm>

namespace Botan {

EC_Scalar EC_Scalar::_from_inner(std::unique_ptr<EC_Scalar_Data> inner) {
   return EC_Scalar(std::move(inner));
}

EC_Scalar::EC_Scalar(std::unique_ptr<EC_Scalar_Data> scalar) : m_scalar(std::move(scalar)) {
   BOTAN_ASSERT_NONNULL(m_scalar);
}

EC_Scalar::EC_Scalar(const EC_Scalar& other) : m_scalar(other.inner().clone()) {}

EC_Scalar::EC_Scalar(EC_Scalar&& other) noexcept : m_scalar(std::move(other.m_scalar)) {}

EC_Scalar& EC_Scalar::operator=(const EC_Scalar& other) {
   if(this != &other) {
      if(m_scalar == nullptr || m_scalar->group() != other.inner().group()) {
         m_scalar = other.inner().clone();
      } else {
         this->assign(other);
      }
   }
   return (*this);
}

EC_Scalar& EC_Scalar::operator=(EC_Scalar&& other) noexcept {
   if(this != &other) {
      // Even a cross-curve swap is accepted here
      std::swap(m_scalar, other.m_scalar);
   }
   return (*this);
}

EC_Scalar::~EC_Scalar() = default;

size_t EC_Scalar::bytes() const {
   return m_scalar->bytes();
}

EC_Scalar EC_Scalar::from_bytes_with_trunc(const EC_Group& group, std::span<const uint8_t> bytes) {
   return EC_Scalar(group._data()->scalar_from_bytes_with_trunc(bytes));
}

EC_Scalar EC_Scalar::from_bytes_mod_order(const EC_Group& group, std::span<const uint8_t> bytes) {
   if(auto s = group._data()->scalar_from_bytes_mod_order(bytes)) {
      return EC_Scalar(std::move(s));
   } else {
      throw Decoding_Error("EC_Scalar::from_bytes_mod_order input invalid");
   }
}

EC_Scalar EC_Scalar::random(const EC_Group& group, RandomNumberGenerator& rng) {
   return EC_Scalar(group._data()->scalar_random(rng));
}

EC_Scalar EC_Scalar::one(const EC_Group& group) {
   return EC_Scalar(group._data()->scalar_one());
}

EC_Scalar EC_Scalar::from_bigint(const EC_Group& group, const BigInt& bn) {
   if(auto data = group._data()->scalar_from_bigint(bn)) {
      return EC_Scalar(std::move(data));
   } else {
      throw Invalid_Argument("EC_Scalar::from_bigint input out of range");
   }
}

BigInt EC_Scalar::to_bigint() const {
   secure_vector<uint8_t> bytes(m_scalar->bytes());
   m_scalar->serialize_to(bytes);
   return BigInt::from_bytes(bytes);
}

//static
EC_Scalar EC_Scalar::gk_x_mod_order(const EC_Scalar& scalar, RandomNumberGenerator& rng) {
   const auto& group = scalar._inner().group();
   return EC_Scalar(group->gk_x_mod_order(scalar.inner(), rng));
}

void EC_Scalar::serialize_to(std::span<uint8_t> bytes) const {
   inner().serialize_to(bytes);
}

void EC_Scalar::serialize_pair_to(std::span<uint8_t> bytes, const EC_Scalar& r, const EC_Scalar& s) {
   BOTAN_ARG_CHECK(r._inner().group() == s._inner().group(), "Curve mismatch");
   const size_t scalar_bytes = r.bytes();
   BOTAN_ARG_CHECK(bytes.size() == 2 * scalar_bytes, "Invalid output length");
   r.serialize_to(bytes.first(scalar_bytes));
   s.serialize_to(bytes.last(scalar_bytes));
}

std::optional<std::pair<EC_Scalar, EC_Scalar>> EC_Scalar::deserialize_pair(const EC_Group& group,
                                                                           std::span<const uint8_t> bytes) {
   if(bytes.size() % 2 != 0) {
      return {};
   }

   const size_t half = bytes.size() / 2;

   auto r = EC_Scalar::deserialize(group, bytes.first(half));
   auto s = EC_Scalar::deserialize(group, bytes.last(half));

   if(r && s) {
      return std::make_pair(r.value(), s.value());
   } else {
      return {};
   }
}

std::optional<EC_Scalar> EC_Scalar::deserialize(const EC_Group& group, std::span<const uint8_t> bytes) {
   if(auto v = group._data()->scalar_deserialize(bytes)) {
      return EC_Scalar(std::move(v));
   } else {
      return {};
   }
}

EC_Scalar::EC_Scalar(const EC_Group& group, std::span<const uint8_t> bytes) {
   m_scalar = group._data()->scalar_deserialize(bytes);
   if(!m_scalar) {
      throw Decoding_Error("EC_Scalar::from_bytes is not a valid scalar value");
   }
}

bool EC_Scalar::is_zero() const {
   return inner().is_zero();
}

EC_Scalar EC_Scalar::invert() const {
   return EC_Scalar(inner().invert());
}

EC_Scalar EC_Scalar::invert_vartime() const {
   return EC_Scalar(inner().invert_vartime());
}

EC_Scalar EC_Scalar::negate() const {
   return EC_Scalar(inner().negate());
}

void EC_Scalar::square_self() {
   m_scalar->square_self();
}

EC_Scalar EC_Scalar::add(const EC_Scalar& x) const {
   BOTAN_ARG_CHECK(inner().group() == x.inner().group(), "Curve mismatch");
   return EC_Scalar(inner().add(x.inner()));
}

EC_Scalar EC_Scalar::sub(const EC_Scalar& x) const {
   BOTAN_ARG_CHECK(inner().group() == x.inner().group(), "Curve mismatch");
   return EC_Scalar(inner().sub(x.inner()));
}

EC_Scalar EC_Scalar::mul(const EC_Scalar& x) const {
   BOTAN_ARG_CHECK(inner().group() == x.inner().group(), "Curve mismatch");
   return EC_Scalar(inner().mul(x.inner()));
}

void EC_Scalar::assign(const EC_Scalar& x) {
   if(this != &x) {
      if(m_scalar == nullptr || m_scalar->group() != x.inner().group()) {
         m_scalar = x.inner().clone();
      } else {
         m_scalar->assign(x.inner());
      }
   }
}

void EC_Scalar::zeroize() {
   m_scalar->zeroize();
}

bool EC_Scalar::is_eq(const EC_Scalar& x) const {
   if(inner().group() != x.inner().group()) {
      return false;
   }

   return inner().is_eq(x.inner());
}

//static
EC_Scalar EC_Scalar::hash(const EC_Group& group,
                          std::string_view hash_fn,
                          std::span<const uint8_t> input,
                          std::span<const uint8_t> domain_sep) {
   /*
   RFC 9380 Section 5.2
      L = ceil((ceil(log2(p)) + k) / 8), where k is the security
      parameter of the suite (e.g., k = 128)
   */
   const size_t scalar_bits = group.get_order_bits();
   const size_t security_level = std::min<size_t>((scalar_bits + 1) / 2, 256);
   secure_vector<uint8_t> uniform_bytes((scalar_bits + security_level + 7) / 8);

   h2c_expand_message(hash_fn, scalar_bits, input, domain_sep)(uniform_bytes);

   return EC_Scalar::from_bytes_mod_order(group, uniform_bytes);
}

}  // namespace Botan
