lib/nostr/keys/private_key.ex

defmodule Nostr.Keys.PrivateKey do
  @moduledoc """
  Private keys management functions
  """

  @doc """
  Creates a new private key

  ## Examples
      iex> Nostr.Keys.PrivateKey.create()
  """
  @spec create() :: K256.Schnorr.signing_key()
  def create do
    K256.Schnorr.generate_random_signing_key()
  end

  @doc """
  Extracts a binary private key from the nsec format

  ## Examples
      iex> nsec = "nsec1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcs5l2exj"
      ...> Nostr.Keys.PrivateKey.from_nsec(nsec)
      {:ok, <<0x6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1::256>>}
  """
  @spec from_nsec(binary()) :: {:ok, binary()} | {:error, binary()}

  def from_nsec("nsec" <> _ = bech32_private_key) do
    case Bech32.decode(bech32_private_key) do
      {:ok, "nsec", private_key} ->
        if bit_size(private_key) == 256 do
          {:ok, private_key}
        else
          {:error, "private key is shorter than 256 bits"}
        end

      {:ok, _, _} ->
        {:error, "malformed bech32 private key"}

      {:error, message} ->
        {:error, message}

      anything ->
        {:error, anything}
    end
  end

  def from_nsec(badly_formatted_address) do
    {:error, "#{badly_formatted_address} is not an nsec formatted address"}
  end

  @doc """
  Extracts a binary private key from the nsec format

  ## Examples
      iex> nsec = "nsec1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcs5l2exj"
      ...> Nostr.Keys.PrivateKey.from_nsec!(nsec)
      <<0x6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1::256>>
  """
  @spec from_nsec!(binary()) :: <<_::256>>
  def from_nsec!("nsec" <> _ = bech32_private_key) do
    case from_nsec(bech32_private_key) do
      {:ok, private_key} -> private_key
      {:error, message} -> raise message
    end
  end

  @doc """
  Encodes a private key into the nsec format

  ## Examples
      iex> private_key = <<0x6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1::256>>
      ...> Nostr.Keys.PrivateKey.to_nsec(private_key)
      "nsec1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcs5l2exj"
  """
  @spec to_nsec(<<_::256>>) :: binary()
  def to_nsec(<<_::256>> = private_key) do
    Bech32.encode("nsec", private_key)
  end

  def to_nsec(not_a_256_bits_private_key) do
    {:error, "#{not_a_256_bits_private_key} should be a 256 bits private key"}
  end

  @doc """
  Does its best to convert any private key format to binary, issues an error if it can't

  ## Examples
      iex> "nsec1fc3d5s6p3hvngdeuhvu2t2cnqkgerg4n55w9uzm8avfngetfgwuqc25heg"
      ...> |> Nostr.Keys.PrivateKey.to_binary()
      { :ok, <<0x4e22da43418dd934373cbb38a5ab13059191a2b3a51c5e0b67eb1334656943b8::256>> }
  """
  @spec to_binary(<<_::256>> | String.t()) :: {:ok, <<_::256>>} | {:error, String.t()}
  def to_binary(<<_::256>> = private_key), do: {:ok, private_key}
  def to_binary("nsec" <> _ = private_key), do: from_nsec(private_key)

  def to_binary(not_lowercase_nsec) do
    case String.downcase(not_lowercase_nsec) do
      "nsec" <> _ = nsec -> from_nsec(nsec)
      _ -> {:error, "#{not_lowercase_nsec} is not a valid private key"}
    end
  end

  @doc """
  Does its best to convert any private key format to binary, raises an error if it can't

  ## Examples
      iex> "nsec1fc3d5s6p3hvngdeuhvu2t2cnqkgerg4n55w9uzm8avfngetfgwuqc25heg"
      ...> |> Nostr.Keys.PrivateKey.to_binary!()
      <<0x4e22da43418dd934373cbb38a5ab13059191a2b3a51c5e0b67eb1334656943b8::256>>
  """
  @spec to_binary!(<<_::256>> | String.t()) :: <<_::256>>
  def to_binary!(private_key) do
    case to_binary(private_key) do
      {:ok, binary_private_key} -> binary_private_key
      {:error, :checksum_failed} -> raise "checkum failed"
      {:error, message} -> raise message
    end
  end
end