lib/eip712.ex

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

  import EIP712.Util, only: [encode_bytes: 2]

  @doc """
  Sign a message.

      iex> %EIP712.Typed{
      ...>   domain: %EIP712.Typed.Domain{
      ...>     chain_id: 1,
      ...>     name: "Test",
      ...>     verifying_contract: EIP712.Util.decode_hex!("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"),
      ...>     version: "1"
      ...>   },
      ...>   types: %{
      ...>     "Test" => %EIP712.Typed.Type{fields: [{"items", {:array, :string}}]}
      ...>   },
      ...>   value: %{
      ...>     "items" => ["item1", "item2"]
      ...>   }
      ...> }
      ...> |> EIP712.sign!(
      ...>   EIP712.Util.decode_hex!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"),
      ...>   hex?: true
      ...> )
      "0x97ce47cfb1497f72019606ba462c3ab4e3552c4225f3b7b75ca42c5787a19b7c29d53b9fe402102a82ea782e806224f819b326b74f98049fe59486640d6fa2911c"
  """
  @spec sign!(EIP712.Typed.t() | String.t(), binary(), Keyword.t()) :: binary()
  def sign!(message, priv_key, opts \\ []) do
    {:ok, sig} = sign(message, priv_key, opts)
    sig
  end

  def sign(message, priv_key, opts \\ [])

  @spec sign(EIP712.Typed.t(), binary(), Keyword.t()) :: {:ok, binary()} | {:error, String.t()}
  def sign(%EIP712.Typed{} = typed, priv_key, opts) do
    typed
    |> EIP712.Typed.encode()
    |> EIP712.Hash.keccak()
    |> sign(priv_key, opts)
  end

  @spec sign(String.t(), binary(), Keyword.t()) :: {:ok, binary()} | {:error, String.t()}
  def sign(message, priv_key, opts) do
    chain_id = Keyword.get(opts, :chain_id, 0)
    hex? = Keyword.get(opts, :hex?, false)

    with key <- Curvy.Key.from_privkey(priv_key),
         sig_bin <- Curvy.sign(message, key, hash: :keccak, compact: true),
         %Curvy.Signature{crv: :secp256k1, r: r, recid: recid, s: s} <-
           Curvy.Signature.parse(sig_bin) do
      # EIP-155
      v = if chain_id == 0, do: 27 + recid, else: chain_id * 2 + 35 + recid

      sig_bin = encode_bytes(r, 32) <> encode_bytes(s, 32) <> encode_bytes(v, 1)

      if hex? do
        {:ok, EIP712.Util.encode_hex(sig_bin)}
      else
        {:ok, sig_bin}
      end
    end
  end
end