lib/eth_wallet.ex

defmodule EthWallet do
  @moduledoc """
    Documentation for `EthWallet`.

    - 1. keys operations

    - 2. generate signed tx

      tx generated path:

      build_tx -> hash_for_signing(serialize -> rlp encode -> kec) -> gen sig -> get signed tx

      -> to raw tx-> send tx to node
  """

  alias EthWallet.Utils.Crypto
  alias EthWallet.Transaction
  alias EthWallet.Web3x.Wallet

  @doc """
    generate keys with/without privkey.

    ## Examples

    iex> EthWallet.generate_keys()

    %{
      addr: "0xd705f740d934acfb27df7bf71aadc00f20d03c7a",
      priv: <<21, ..., 166>>,
      pub: <<4, ..., 165, ...>>
    }

  """
  def generate_keys() do
    %{pub: pub}
      = result
      = Crypto.generate_key_secp256k1()
    do_generate_keys(result, pub)
  end

  @doc """
    iex> EthWallet.generate_keys(<<21, ..., 166>>)

    %{
      addr: "0xd705f740d934acfb27df7bf71aadc00f20d03c7a",
      priv: <<21, ..., 166, ...>>,
      pub: <<4, ..., 165, ...>>
    }
  """
  @spec generate_keys(binary()) :: map()
  def generate_keys(priv) do
    %{pub: pub}
      = result
      = Crypto.generate_key_secp256k1(priv)
    do_generate_keys(result, pub)
  end

  defp do_generate_keys(result, pub) do
    addr = Crypto.pubkey_to_address(pub)
    Map.put(result, :addr, addr)
  end

  @doc """
    encrypt key with password.

    ## Examples

    iex> EthWallet.encrypt_key("ddd", "abc")

    <<40, 28, 220, 122, 235, 180, 216, 145, 142, 171, 53, 146, 25, 136, 47, 215>>

  """
  defdelegate encrypt_key(encrypted_key, password), to: Crypto

  @doc """
    decrypt key with password.

    ## Examples

    iex> EthWallet.decrypt_key(payload, "bbc")

    "ddd"
  """
  defdelegate decrypt_key(payload, password), to: Crypto

  @doc """
    build transaction
  """
  @spec build_tx(String.t(), integer(), binary(), integer(), integer(), integer()) :: Transction.t()
  defdelegate build_tx(to_str, value, data, nonce, gas_price, gas_limit), to: Transaction

  @doc """
    sign transaction
  """
  @spec sign_tx(Transaction.t(), binary(), integer() | nil) :: Transaction.t()
  defdelegate sign_tx(tx, private_key, chain_id \\ nil), to: Transaction

  @doc """
    signed transaction to raw transaction
  """
  @spec signed_tx_to_raw_tx(Transaction.t()) :: String.t()
  defdelegate signed_tx_to_raw_tx(signed_tx), to: Transaction

  @doc """
    sign uncompact, as sign in bitcoin.
  """
  @spec sign(binary(), binary()) :: binary()
  defdelegate sign(digest, privkey), to: Crypto

  @doc """
    verify uncompact, fit to sign()
  """
  @spec verify(binary(), binary(), binary()) :: boolean()
  defdelegate verify(digest, sig, pubkey), to: Crypto

  # +----------------------+
  # | Ethereum Sign/Verify |
  # +----------------------+
  @doc """
    sign compact, as sign in ethereum.
  """
  @spec sign_compact(<<_ :: 256>>, <<_ :: 256>>, nil | integer()) :: %{v: integer(), r: integer(), s: integer(), sig: <<_::512>>}
  def sign_compact(digest, privkey, chain_id \\ nil) do
    digest
    |> standard_hash()
    |> Crypto.sign_compact(privkey, chain_id)
  end

    @doc """
    verify by msg, sig and addr, fit to "sign_compact()"
  """
  @spec verify_compact(String.t(), String.t(), String.t()) :: boolean()
  def verify_compact(msg_unhashed, sig, addr) do
    Wallet.verify_message?(addr, msg_unhashed, sig)
  end

  @doc """
    standard hash for msg signature.
  """
  @spec standard_hash(String.t()) :: binary()
  def standard_hash(msg_unhashed), do: Wallet.hash_message(msg_unhashed)

  # @doc """
  #   verify compact, fit to sign_compact()
  # """
  # @spec verify_compact(binary(), binary(), binary()) :: boolean()
  # defdelegate verify_compact(msg, sig, pubkey), to: Crypto

  @doc """
    recover pubkey by recovcery id, generated by sign compact
  """
  @spec recover(binary(), binary(), integer(), nil | integer()) :: {:ok, binary()} | {:error, String.t()}
  defdelegate recover(digest, signature, recovery_id_handled , chain_id \\ nil), to: Crypto

end