lib/bsv/pub_key.ex

defmodule BSV.PubKey do
  @moduledoc """
  A PubKey is a data structure representing a Bitcoin public key.

  Internally, a public key is the `x` and `y` coordiantes of a point of the
  `secp256k1` curve. It is derived by performaing elliptic curve multiplication
  on a corresponding private key.
  """
  alias BSV.PrivKey
  alias Curvy.{Key, Point}
  import BSV.Util, only: [decode: 2, encode: 2]

  defstruct point: nil, compressed: true

  @typedoc "Public key struct"
  @type t() :: %__MODULE__{
    point: Point.t(),
    compressed: boolean()
  }

  @doc """
  Parses the given binary into a `t:BSV.PubKey.t/0`.

  Returns the result in an `:ok` / `:error` tuple pair.

  ## Options

  The accepted options are:

  * `:encoding` - Optionally decode the binary with either the `:base64` or `:hex` encoding scheme.

  ## Examples

      iex> PubKey.from_binary("03f81f8c8b90f5ec06ee4245eab166e8af903fc73a6dd73636687ef027870abe39", encoding: :hex)
      {:ok, %PubKey{
        compressed: true,
        point: %Curvy.Point{
          x: 112229328714845468078961951285525025245993969218674417992740440691709714284089,
          y: 691772308660403791193362590139379363593914935665750098177712560871566383255
        }
      }}
  """
  @spec from_binary(binary(), keyword()) :: {:ok, t()} | {:error, term()}
  def from_binary(pubkey, opts \\ []) when is_binary(pubkey) do
    encoding = Keyword.get(opts, :encoding)

    with {:ok, pubkey} when byte_size(pubkey) in [33, 65] <- decode(pubkey, encoding) do
      %Key{point: point, compressed: compressed} = Key.from_pubkey(pubkey)
      {:ok, struct(__MODULE__, point: point, compressed: compressed)}
    else
      {:ok, pubkey} ->
        {:error, {:invalid_pubkey, byte_size(pubkey)}}
      error ->
        error
    end
  end

  @doc """
  Parses the given binary into a `t:BSV.PubKey.t/0`.

  As `from_binary/2` but returns the result or raises an exception.
  """
  @spec from_binary!(binary(), keyword()) :: t()
  def from_binary!(pubkey, opts \\ []) when is_binary(pubkey) do
    case from_binary(pubkey, opts) do
      {:ok, pubkey} ->
        pubkey
      {:error, {:invalid_pubkey, _length} = error} ->
        raise BSV.DecodeError, error
      {:error, error} ->
        raise error
    end
  end

  @doc """
  Returns a `t:BSV.PubKey.t/0` derived from the given `t:BSV.PrivKey.t/0`.
  """
  @spec from_privkey(PrivKey.t()) :: t()
  def from_privkey(%PrivKey{d: d, compressed: compressed}) do
    {pubkey, _privkey} = :crypto.generate_key(:ecdh, :secp256k1, d)
    %Key{point: point} = Key.from_pubkey(pubkey)
    struct(__MODULE__, point: point, compressed: compressed)
  end

  @doc """
  Serialises the given `t:BSV.PrivKey.t/0` into a binary.

  ## Options

  The accepted options are:

  * `:encoding` - Optionally encode the binary with either the `:base64` or `:hex` encoding scheme.

  ## Examples

      iex> PubKey.to_binary(@pubkey, encoding: :hex)
      "03f81f8c8b90f5ec06ee4245eab166e8af903fc73a6dd73636687ef027870abe39"
  """
  @spec to_binary(t(), keyword()) :: binary()
  def to_binary(%__MODULE__{point: point, compressed: compressed}, opts \\ []) do
    encoding = Keyword.get(opts, :encoding)
    %Key{point: point, compressed: compressed}
    |> Key.to_pubkey()
    |> encode(encoding)
  end

end