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

#ifndef BOTAN_EC_APOINT_H_
#define BOTAN_EC_APOINT_H_

#include <botan/concepts.h>
#include <botan/ec_point_format.h>
#include <botan/secmem.h>
#include <botan/types.h>
#include <memory>
#include <optional>
#include <span>
#include <string_view>
#include <vector>

namespace Botan {

class BigInt;
class RandomNumberGenerator;
class EC_Group;
class EC_Scalar;

#if defined(BOTAN_HAS_LEGACY_EC_POINT)
class EC_Point;
#endif

class EC_Group_Data;
class EC_AffinePoint_Data;

/// Elliptic Curve Point in Affine Representation
///
class BOTAN_PUBLIC_API(3, 6) EC_AffinePoint final {
   public:
      /// Point deserialization. Throws if wrong length or not a valid point
      ///
      /// This accepts SEC1 compressed or uncompressed formats
      EC_AffinePoint(const EC_Group& group, std::span<const uint8_t> bytes);

      /// Point deserialization. Returns nullopt if wrong length or not a valid point
      ///
      /// This accepts SEC1 compressed or uncompressed formats
      static std::optional<EC_AffinePoint> deserialize(const EC_Group& group, std::span<const uint8_t> bytes);

      /// Create a point from a pair (x,y) of integers
      ///
      /// The integers must be within the field - in the range [0,p) and must
      /// satisfy the curve equation
      static std::optional<EC_AffinePoint> from_bigint_xy(const EC_Group& group, const BigInt& x, const BigInt& y);

      /// Multiply by the group generator returning a complete point
      static EC_AffinePoint g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng);

      /// Return the identity element
      static EC_AffinePoint identity(const EC_Group& group);

      /// Return the standard group generator
      static EC_AffinePoint generator(const EC_Group& group);

      /// Hash to curve (RFC 9380), random oracle variant
      ///
      /// Only supported for specific groups
      static EC_AffinePoint hash_to_curve_ro(const EC_Group& group,
                                             std::string_view hash_fn,
                                             std::span<const uint8_t> input,
                                             std::span<const uint8_t> domain_sep);

      /// Hash to curve (RFC 9380), non uniform variant
      ///
      /// Only supported for specific groups
      static EC_AffinePoint hash_to_curve_nu(const EC_Group& group,
                                             std::string_view hash_fn,
                                             std::span<const uint8_t> input,
                                             std::span<const uint8_t> domain_sep);

      /// Multiply a point by a scalar returning a complete point
      EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng) const;

      /// Multiply a point by a scalar, returning the byte encoding of the x coordinate only
      secure_vector<uint8_t> mul_x_only(const EC_Scalar& scalar, RandomNumberGenerator& rng) const;

      /// Compute 2-ary multiscalar multiplication - p*x + q*y
      ///
      /// This operation runs in constant time with respect to p, x, q, and y
      ///
      /// @returns p*x+q*y, or nullopt if the result was the point at infinity
      static std::optional<EC_AffinePoint> mul_px_qy(const EC_AffinePoint& p,
                                                     const EC_Scalar& x,
                                                     const EC_AffinePoint& q,
                                                     const EC_Scalar& y,
                                                     RandomNumberGenerator& rng);

      /// Point addition
      ///
      /// Note that this is quite slow since it converts the resulting
      /// projective point immediately to affine coordinates, which requires a
      /// field inversion. This can be sufficient when implementing protocols
      /// that just need to perform a few additions.
      ///
      /// In the future a cooresponding EC_ProjectivePoint type may be added
      /// which would avoid the expensive affine conversions
      EC_AffinePoint add(const EC_AffinePoint& q) const;

      /// Point negation
      EC_AffinePoint negate() const;

      /// Return the number of bytes of a field element
      ///
      /// A point consists of two field elements, plus possibly a header
      size_t field_element_bytes() const;

      /// Return true if this point is the identity element
      bool is_identity() const;

      /// Write the fixed length encoding of affine x coordinate
      ///
      /// The output span must be exactly field_element_bytes long
      ///
      /// This function will fail if this point is the identity element
      void serialize_x_to(std::span<uint8_t> bytes) const;

      /// Write the fixed length encoding of affine y coordinate
      ///
      /// The output span must be exactly field_element_bytes long
      ///
      /// This function will fail if this point is the identity element
      void serialize_y_to(std::span<uint8_t> bytes) const;

      /// Write the fixed length encoding of affine x and y coordinates
      ///
      /// The output span must be exactly 2*field_element_bytes long
      ///
      /// This function will fail if this point is the identity element
      void serialize_xy_to(std::span<uint8_t> bytes) const;

      /// Write the fixed length SEC1 compressed encoding
      ///
      /// The output span must be exactly 1 + field_element_bytes long
      ///
      /// This function will fail if this point is the identity element
      void serialize_compressed_to(std::span<uint8_t> bytes) const;

      /// Return the fixed length encoding of SEC1 uncompressed encoding
      ///
      /// The output span must be exactly 1 + 2*field_element_bytes long
      ///
      /// This function will fail if this point is the identity element
      void serialize_uncompressed_to(std::span<uint8_t> bytes) const;

      /// Return the bytes of the affine x coordinate in a container
      ///
      /// This function will fail if this point is the identity element
      template <concepts::resizable_byte_buffer T = secure_vector<uint8_t>>
      T x_bytes() const {
         T bytes(this->field_element_bytes());
         this->serialize_x_to(bytes);
         return bytes;
      }

      /// Return the bytes of the affine y coordinate in a container
      ///
      /// This function will fail if this point is the identity element
      template <concepts::resizable_byte_buffer T = secure_vector<uint8_t>>
      T y_bytes() const {
         T bytes(this->field_element_bytes());
         this->serialize_y_to(bytes);
         return bytes;
      }

      /// Return the bytes of the affine x and y coordinates in a container
      ///
      /// This function will fail if this point is the identity element
      template <concepts::resizable_byte_buffer T = secure_vector<uint8_t>>
      T xy_bytes() const {
         T bytes(2 * this->field_element_bytes());
         this->serialize_xy_to(bytes);
         return bytes;
      }

      /// Return the bytes of the affine x and y coordinates in a container
      ///
      /// This function will fail if this point is the identity element
      template <concepts::resizable_byte_buffer T = std::vector<uint8_t>>
      T serialize_uncompressed() const {
         T bytes(1 + 2 * this->field_element_bytes());
         this->serialize_uncompressed_to(bytes);
         return bytes;
      }

      /// Return the bytes of the affine x and y coordinates in a container
      ///
      /// This function will fail if this point is the identity element
      template <concepts::resizable_byte_buffer T = std::vector<uint8_t>>
      T serialize_compressed() const {
         T bytes(1 + this->field_element_bytes());
         this->serialize_compressed_to(bytes);
         return bytes;
      }

      bool operator==(const EC_AffinePoint& other) const;

      bool operator!=(const EC_AffinePoint& other) const { return !(*this == other); }

      /// Return an encoding depending on the requested format
      std::vector<uint8_t> serialize(EC_Point_Format format) const;

      EC_AffinePoint(const EC_AffinePoint& other);
      EC_AffinePoint(EC_AffinePoint&& other) noexcept;

      EC_AffinePoint& operator=(const EC_AffinePoint& other);
      EC_AffinePoint& operator=(EC_AffinePoint&& other) noexcept;

#if defined(BOTAN_HAS_LEGACY_EC_POINT)
      /**
      * Deprecated conversion
      */
      EC_AffinePoint(const EC_Group& group, const EC_Point& pt);

      /**
      * Deprecated conversion
      */
      EC_Point to_legacy_point() const;
#endif

      BOTAN_DEPRECATED("Use version without workspace arg")
      static EC_AffinePoint g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector<BigInt>&) {
         return EC_AffinePoint::g_mul(scalar, rng);
      }

      BOTAN_DEPRECATED("Use version without workspace arg")
      EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector<BigInt>&) const {
         return this->mul(scalar, rng);
      }

      /// Multiply a point by a scalar, returning the byte encoding of the x coordinate only
      secure_vector<uint8_t> mul_x_only(const EC_Scalar& scalar,
                                        RandomNumberGenerator& rng,
                                        std::vector<BigInt>&) const {
         return this->mul_x_only(scalar, rng);
      }

      ~EC_AffinePoint();

      const EC_AffinePoint_Data& _inner() const { return inner(); }

      static EC_AffinePoint _from_inner(std::unique_ptr<EC_AffinePoint_Data> inner);

      const std::shared_ptr<const EC_Group_Data>& _group() const;

   private:
      friend class EC_Mul2Table;

      EC_AffinePoint(std::unique_ptr<EC_AffinePoint_Data> point);

      const EC_AffinePoint_Data& inner() const { return *m_point; }

      std::unique_ptr<EC_AffinePoint_Data> m_point;
};

}  // namespace Botan

#endif
